summaryrefslogtreecommitdiffstats
path: root/mailnews/addrbook/src
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2019-11-03 00:17:46 -0400
committerMatt A. Tobin <email@mattatobin.com>2019-11-03 00:17:46 -0400
commit302bf1b523012e11b60425d6eee1221ebc2724eb (patch)
treeb191a895f8716efcbe42f454f37597a545a6f421 /mailnews/addrbook/src
parent21b3f6247403c06f85e1f45d219f87549862198f (diff)
downloadUXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.gz
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.lz
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.xz
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.zip
Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1
Diffstat (limited to 'mailnews/addrbook/src')
-rw-r--r--mailnews/addrbook/src/moz.build93
-rw-r--r--mailnews/addrbook/src/nsAbAddressCollector.cpp331
-rw-r--r--mailnews/addrbook/src/nsAbAddressCollector.h44
-rw-r--r--mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js58
-rw-r--r--mailnews/addrbook/src/nsAbAutoCompleteSearch.js466
-rw-r--r--mailnews/addrbook/src/nsAbBSDirectory.cpp323
-rw-r--r--mailnews/addrbook/src/nsAbBSDirectory.h50
-rw-r--r--mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp246
-rw-r--r--mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h45
-rw-r--r--mailnews/addrbook/src/nsAbBooleanExpression.cpp132
-rw-r--r--mailnews/addrbook/src/nsAbBooleanExpression.h43
-rw-r--r--mailnews/addrbook/src/nsAbCardProperty.cpp1193
-rw-r--r--mailnews/addrbook/src/nsAbCardProperty.h59
-rw-r--r--mailnews/addrbook/src/nsAbContentHandler.cpp184
-rw-r--r--mailnews/addrbook/src/nsAbContentHandler.h26
-rw-r--r--mailnews/addrbook/src/nsAbDirFactoryService.cpp54
-rw-r--r--mailnews/addrbook/src/nsAbDirFactoryService.h23
-rw-r--r--mailnews/addrbook/src/nsAbDirProperty.cpp593
-rw-r--r--mailnews/addrbook/src/nsAbDirProperty.h72
-rw-r--r--mailnews/addrbook/src/nsAbDirectoryQuery.cpp528
-rw-r--r--mailnews/addrbook/src/nsAbDirectoryQuery.h113
-rw-r--r--mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp33
-rw-r--r--mailnews/addrbook/src/nsAbDirectoryQueryProxy.h27
-rw-r--r--mailnews/addrbook/src/nsAbLDAPAttributeMap.js247
-rw-r--r--mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js325
-rw-r--r--mailnews/addrbook/src/nsAbLDAPCard.cpp297
-rw-r--r--mailnews/addrbook/src/nsAbLDAPCard.h30
-rw-r--r--mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp542
-rw-r--r--mailnews/addrbook/src/nsAbLDAPChangeLogData.h57
-rw-r--r--mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp180
-rw-r--r--mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h28
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirFactory.cpp79
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirFactory.h23
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectory.cpp948
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectory.h75
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp372
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectoryModify.h31
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp610
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h44
-rw-r--r--mailnews/addrbook/src/nsAbLDAPListenerBase.cpp358
-rw-r--r--mailnews/addrbook/src/nsAbLDAPListenerBase.h54
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationData.cpp489
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationData.h66
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp153
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationQuery.h44
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationService.cpp136
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationService.h33
-rw-r--r--mailnews/addrbook/src/nsAbLDIFService.cpp868
-rw-r--r--mailnews/addrbook/src/nsAbLDIFService.h37
-rw-r--r--mailnews/addrbook/src/nsAbMDBCard.cpp55
-rw-r--r--mailnews/addrbook/src/nsAbMDBCard.h26
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirFactory.cpp118
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirFactory.h24
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirProperty.cpp145
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirProperty.h40
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirectory.cpp1125
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirectory.h104
-rw-r--r--mailnews/addrbook/src/nsAbManager.cpp1422
-rw-r--r--mailnews/addrbook/src/nsAbManager.h71
-rw-r--r--mailnews/addrbook/src/nsAbOSXCard.h47
-rw-r--r--mailnews/addrbook/src/nsAbOSXCard.mm401
-rw-r--r--mailnews/addrbook/src/nsAbOSXDirFactory.cpp50
-rw-r--r--mailnews/addrbook/src/nsAbOSXDirFactory.h21
-rw-r--r--mailnews/addrbook/src/nsAbOSXDirectory.h126
-rw-r--r--mailnews/addrbook/src/nsAbOSXDirectory.mm1374
-rw-r--r--mailnews/addrbook/src/nsAbOSXUtils.h36
-rw-r--r--mailnews/addrbook/src/nsAbOSXUtils.mm117
-rw-r--r--mailnews/addrbook/src/nsAbOutlookDirFactory.cpp87
-rw-r--r--mailnews/addrbook/src/nsAbOutlookDirFactory.h22
-rw-r--r--mailnews/addrbook/src/nsAbOutlookDirectory.cpp1539
-rw-r--r--mailnews/addrbook/src/nsAbOutlookDirectory.h152
-rw-r--r--mailnews/addrbook/src/nsAbQueryStringToExpression.cpp337
-rw-r--r--mailnews/addrbook/src/nsAbQueryStringToExpression.h49
-rw-r--r--mailnews/addrbook/src/nsAbUtils.h140
-rw-r--r--mailnews/addrbook/src/nsAbView.cpp1451
-rw-r--r--mailnews/addrbook/src/nsAbView.h83
-rw-r--r--mailnews/addrbook/src/nsAbWinHelper.cpp1003
-rw-r--r--mailnews/addrbook/src/nsAbWinHelper.h156
-rw-r--r--mailnews/addrbook/src/nsAddbookProtocolHandler.cpp323
-rw-r--r--mailnews/addrbook/src/nsAddbookProtocolHandler.h45
-rw-r--r--mailnews/addrbook/src/nsAddbookUrl.cpp282
-rw-r--r--mailnews/addrbook/src/nsAddbookUrl.h39
-rw-r--r--mailnews/addrbook/src/nsAddrDatabase.cpp3335
-rw-r--r--mailnews/addrbook/src/nsAddrDatabase.h439
-rw-r--r--mailnews/addrbook/src/nsAddrbook.manifest12
-rw-r--r--mailnews/addrbook/src/nsDirPrefs.cpp1452
-rw-r--r--mailnews/addrbook/src/nsDirPrefs.h86
-rw-r--r--mailnews/addrbook/src/nsMapiAddressBook.cpp147
-rw-r--r--mailnews/addrbook/src/nsMapiAddressBook.h54
-rw-r--r--mailnews/addrbook/src/nsMsgVCardService.cpp77
-rw-r--r--mailnews/addrbook/src/nsMsgVCardService.h24
-rw-r--r--mailnews/addrbook/src/nsVCard.cpp1571
-rw-r--r--mailnews/addrbook/src/nsVCard.h64
-rw-r--r--mailnews/addrbook/src/nsVCardObj.cpp1330
-rw-r--r--mailnews/addrbook/src/nsVCardObj.h396
-rw-r--r--mailnews/addrbook/src/nsWabAddressBook.cpp128
-rw-r--r--mailnews/addrbook/src/nsWabAddressBook.h57
97 files changed, 31044 insertions, 0 deletions
diff --git a/mailnews/addrbook/src/moz.build b/mailnews/addrbook/src/moz.build
new file mode 100644
index 000000000..648958cf2
--- /dev/null
+++ b/mailnews/addrbook/src/moz.build
@@ -0,0 +1,93 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsAbDirProperty.h',
+ 'nsDirPrefs.h',
+ 'nsVCardObj.h',
+]
+
+SOURCES += [
+ 'nsAbAddressCollector.cpp',
+ 'nsAbBooleanExpression.cpp',
+ 'nsAbBSDirectory.cpp',
+ 'nsAbCardProperty.cpp',
+ 'nsAbContentHandler.cpp',
+ 'nsAbDirectoryQuery.cpp',
+ 'nsAbDirectoryQueryProxy.cpp',
+ 'nsAbDirFactoryService.cpp',
+ 'nsAbDirProperty.cpp',
+ 'nsAbLDIFService.cpp',
+ 'nsAbManager.cpp',
+ 'nsAbMDBCard.cpp',
+ 'nsAbMDBDirectory.cpp',
+ 'nsAbMDBDirFactory.cpp',
+ 'nsAbMDBDirProperty.cpp',
+ 'nsAbQueryStringToExpression.cpp',
+ 'nsAbView.cpp',
+ 'nsAddbookProtocolHandler.cpp',
+ 'nsAddbookUrl.cpp',
+ 'nsAddrDatabase.cpp',
+ 'nsDirPrefs.cpp',
+ 'nsMsgVCardService.cpp',
+ 'nsVCard.cpp',
+ 'nsVCardObj.cpp',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['MOZ_MAPI_SUPPORT']:
+ SOURCES += [
+ 'nsAbOutlookDirectory.cpp',
+ 'nsAbOutlookDirFactory.cpp',
+ 'nsAbWinHelper.cpp',
+ 'nsMapiAddressBook.cpp',
+ 'nsWabAddressBook.cpp',
+ ]
+
+if CONFIG['OS_ARCH'] == 'Darwin':
+ SOURCES += [
+ 'nsAbOSXDirFactory.cpp',
+ ]
+
+ SOURCES += [
+ 'nsAbOSXCard.mm',
+ 'nsAbOSXDirectory.mm',
+ 'nsAbOSXUtils.mm',
+ ]
+
+if CONFIG['MOZ_LDAP_XPCOM']:
+ SOURCES += [
+ 'nsAbBoolExprToLDAPFilter.cpp',
+ 'nsAbLDAPCard.cpp',
+ 'nsAbLDAPDirectory.cpp',
+ 'nsAbLDAPDirectoryModify.cpp',
+ 'nsAbLDAPDirectoryQuery.cpp',
+ 'nsAbLDAPDirFactory.cpp',
+ 'nsAbLDAPListenerBase.cpp',
+ 'nsAbLDAPReplicationData.cpp',
+ 'nsAbLDAPReplicationQuery.cpp',
+ 'nsAbLDAPReplicationService.cpp',
+ ]
+ # XXX These files are not being built as they don't work. Bug 311632 should
+ # fix them.
+ # nsAbLDAPChangeLogQuery.cpp
+ # nsAbLDAPChangeLogData.cpp
+
+ EXTRA_COMPONENTS += [
+ 'nsAbLDAPAutoCompleteSearch.js',
+ ]
+
+ DEFINES['MOZ_LDAP_XPCOM'] = True
+
+EXTRA_COMPONENTS += [
+ 'nsAbAutoCompleteMyDomain.js',
+ 'nsAbAutoCompleteSearch.js',
+ 'nsAbLDAPAttributeMap.js',
+]
+
+EXTRA_PP_COMPONENTS += [
+ 'nsAddrbook.manifest',
+]
+
+FINAL_LIBRARY = 'mail'
diff --git a/mailnews/addrbook/src/nsAbAddressCollector.cpp b/mailnews/addrbook/src/nsAbAddressCollector.cpp
new file mode 100644
index 000000000..60f359601
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbAddressCollector.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for pre-compiled headers
+#include "nsISimpleEnumerator.h"
+
+#include "nsIAbCard.h"
+#include "nsAbBaseCID.h"
+#include "nsAbAddressCollector.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsStringGlue.h"
+#include "prmem.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAbManager.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+using namespace mozilla::mailnews;
+
+NS_IMPL_ISUPPORTS(nsAbAddressCollector, nsIAbAddressCollector, nsIObserver)
+
+#define PREF_MAIL_COLLECT_ADDRESSBOOK "mail.collect_addressbook"
+
+nsAbAddressCollector::nsAbAddressCollector()
+{
+}
+
+nsAbAddressCollector::~nsAbAddressCollector()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ pPrefBranchInt->RemoveObserver(PREF_MAIL_COLLECT_ADDRESSBOOK, this);
+}
+
+/**
+ * Returns the first card found with the specified email address. This
+ * returns an already addrefed pointer to the card if the card is found.
+ */
+already_AddRefed<nsIAbCard>
+nsAbAddressCollector::GetCardForAddress(const nsACString &aEmailAddress,
+ nsIAbDirectory **aDirectory)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = abManager->GetDirectories(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ bool hasMore;
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIAbDirectory> directory;
+ nsCOMPtr<nsIAbCard> result;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ directory = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ // Some implementations may return NS_ERROR_NOT_IMPLEMENTED here,
+ // so just catch the value and continue.
+ if (NS_FAILED(directory->CardForEmailAddress(aEmailAddress,
+ getter_AddRefs(result))))
+ {
+ continue;
+ }
+
+ if (result)
+ {
+ if (aDirectory)
+ directory.forget(aDirectory);
+ return result.forget();
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsAbAddressCollector::CollectAddress(const nsACString &aAddresses,
+ bool aCreateCard,
+ uint32_t aSendFormat)
+{
+ // If we've not got a valid directory, no point in going any further
+ if (!mDirectory)
+ return NS_OK;
+
+ // note that we're now setting the whole recipient list,
+ // not just the pretty name of the first recipient.
+ nsTArray<nsCString> names;
+ nsTArray<nsCString> addresses;
+ ExtractAllAddresses(EncodedHeader(aAddresses),
+ UTF16ArrayAdapter<>(names), UTF16ArrayAdapter<>(addresses));
+ uint32_t numAddresses = names.Length();
+
+ for (uint32_t i = 0; i < numAddresses; i++)
+ {
+ // Don't allow collection of addresses with no email address, it makes
+ // no sense. Whilst we should never get here in most normal cases, we
+ // should still be careful.
+ if (addresses[i].IsEmpty())
+ continue;
+
+ CollectSingleAddress(addresses[i], names[i], aCreateCard, aSendFormat,
+ false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbAddressCollector::CollectSingleAddress(const nsACString &aEmail,
+ const nsACString &aDisplayName,
+ bool aCreateCard,
+ uint32_t aSendFormat,
+ bool aSkipCheckExisting)
+{
+ if (!mDirectory)
+ return NS_OK;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbDirectory> originDirectory;
+ nsCOMPtr<nsIAbCard> card = (!aSkipCheckExisting) ?
+ GetCardForAddress(aEmail, getter_AddRefs(originDirectory)) : nullptr;
+
+ if (!card && (aCreateCard || aSkipCheckExisting))
+ {
+ card = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && card)
+ {
+ // Set up the fields for the new card.
+ SetNamesForCard(card, aDisplayName);
+ AutoCollectScreenName(card, aEmail);
+
+ if (NS_SUCCEEDED(card->SetPrimaryEmail(NS_ConvertUTF8toUTF16(aEmail))))
+ {
+ card->SetPropertyAsUint32(kPreferMailFormatProperty, aSendFormat);
+
+ nsCOMPtr<nsIAbCard> addedCard;
+ rv = mDirectory->AddCard(card, getter_AddRefs(addedCard));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add card");
+ }
+ }
+ }
+ else if (card && originDirectory)
+ {
+ // It could be that the origin directory is read-only, so don't try and
+ // write to it if it is.
+ bool readOnly;
+ rv = originDirectory->GetReadOnly(&readOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (readOnly)
+ return NS_OK;
+
+ // address is already in the AB, so update the names
+ bool modifiedCard = false;
+
+ nsString displayName;
+ card->GetDisplayName(displayName);
+ // If we already have a display name, don't set the names on the card.
+ if (displayName.IsEmpty() && !aDisplayName.IsEmpty())
+ modifiedCard = SetNamesForCard(card, aDisplayName);
+
+ if (aSendFormat != nsIAbPreferMailFormat::unknown)
+ {
+ uint32_t currentFormat;
+ rv = card->GetPropertyAsUint32(kPreferMailFormatProperty,
+ &currentFormat);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get preferred mail format");
+
+ // we only want to update the AB if the current format is unknown
+ if (currentFormat == nsIAbPreferMailFormat::unknown &&
+ NS_SUCCEEDED(card->SetPropertyAsUint32(kPreferMailFormatProperty,
+ aSendFormat)))
+ modifiedCard = true;
+ }
+
+ if (modifiedCard)
+ originDirectory->ModifyCard(card);
+ }
+
+ return NS_OK;
+}
+
+// Works out the screen name to put on the card for some well-known addresses
+void
+nsAbAddressCollector::AutoCollectScreenName(nsIAbCard *aCard,
+ const nsACString &aEmail)
+{
+ if (!aCard)
+ return;
+
+ int32_t atPos = aEmail.FindChar('@');
+ if (atPos == -1)
+ return;
+
+ const nsACString& domain = Substring(aEmail, atPos + 1);
+
+ if (domain.IsEmpty())
+ return;
+ // username in
+ // username@aol.com (America Online)
+ // username@cs.com (Compuserve)
+ // username@netscape.net (Netscape webmail)
+ // are all AIM screennames. autocollect that info.
+ if (domain.Equals("aol.com") || domain.Equals("cs.com") ||
+ domain.Equals("netscape.net"))
+ aCard->SetPropertyAsAUTF8String(kScreenNameProperty, Substring(aEmail, 0, atPos));
+ else if (domain.Equals("gmail.com") || domain.Equals("googlemail.com"))
+ aCard->SetPropertyAsAUTF8String(kGtalkProperty, Substring(aEmail, 0, atPos));
+}
+
+// Returns true if the card was modified successfully.
+bool
+nsAbAddressCollector::SetNamesForCard(nsIAbCard *aSenderCard,
+ const nsACString &aFullName)
+{
+ nsCString firstName;
+ nsCString lastName;
+ bool modifiedCard = false;
+
+ if (NS_SUCCEEDED(aSenderCard->SetDisplayName(NS_ConvertUTF8toUTF16(aFullName))))
+ modifiedCard = true;
+
+ // Now split up the full name.
+ SplitFullName(nsCString(aFullName), firstName, lastName);
+
+ if (!firstName.IsEmpty() &&
+ NS_SUCCEEDED(aSenderCard->SetFirstName(NS_ConvertUTF8toUTF16(firstName))))
+ modifiedCard = true;
+
+ if (!lastName.IsEmpty() &&
+ NS_SUCCEEDED(aSenderCard->SetLastName(NS_ConvertUTF8toUTF16(lastName))))
+ modifiedCard = true;
+
+ if (modifiedCard)
+ aSenderCard->SetPropertyAsBool("PreferDisplayName", false);
+
+ return modifiedCard;
+}
+
+// Splits the first and last name based on the space between them.
+void
+nsAbAddressCollector::SplitFullName(const nsCString &aFullName, nsCString &aFirstName,
+ nsCString &aLastName)
+{
+ int index = aFullName.RFindChar(' ');
+ if (index != -1)
+ {
+ aLastName = Substring(aFullName, index + 1);
+ aFirstName = Substring(aFullName, 0, index);
+ }
+}
+
+// Observes the collected address book pref in case it changes.
+NS_IMETHODIMP
+nsAbAddressCollector::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ if (!prefBranch) {
+ NS_ASSERTION(prefBranch, "failed to get prefs");
+ return NS_OK;
+ }
+
+ SetUpAbFromPrefs(prefBranch);
+ return NS_OK;
+}
+
+// Initialises the collector with the required items.
+nsresult
+nsAbAddressCollector::Init(void)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = prefBranch->AddObserver(PREF_MAIL_COLLECT_ADDRESSBOOK, this, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetUpAbFromPrefs(prefBranch);
+ return NS_OK;
+}
+
+// Performs the necessary changes to set up the collector for the specified
+// collected address book.
+void
+nsAbAddressCollector::SetUpAbFromPrefs(nsIPrefBranch *aPrefBranch)
+{
+ nsCString abURI;
+ aPrefBranch->GetCharPref(PREF_MAIL_COLLECT_ADDRESSBOOK,
+ getter_Copies(abURI));
+
+ if (abURI.IsEmpty())
+ abURI.AssignLiteral(kPersonalAddressbookUri);
+
+ if (abURI == mABURI)
+ return;
+
+ mDirectory = nullptr;
+ mABURI = abURI;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = abManager->GetDirectory(mABURI, getter_AddRefs(mDirectory));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ bool readOnly;
+ rv = mDirectory->GetReadOnly(&readOnly);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // If the directory is read-only, we can't write to it, so just blank it out
+ // here, and warn because we shouldn't hit this (UI is wrong).
+ if (readOnly)
+ {
+ NS_ERROR("Address Collection book preferences is set to a read-only book. "
+ "Address collection will not take place.");
+ mDirectory = nullptr;
+ }
+}
diff --git a/mailnews/addrbook/src/nsAbAddressCollector.h b/mailnews/addrbook/src/nsAbAddressCollector.h
new file mode 100644
index 000000000..7ef2236b8
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbAddressCollector.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsAbAddressCollector_H_
+#define _nsAbAddressCollector_H_
+
+#include "nsIAbAddressCollector.h"
+#include "nsCOMPtr.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbCard.h"
+#include "nsIObserver.h"
+#include "nsStringGlue.h"
+
+class nsIPrefBranch;
+
+class nsAbAddressCollector : public nsIAbAddressCollector,
+ public nsIObserver
+{
+public:
+ nsAbAddressCollector();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABADDRESSCOLLECTOR
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+private:
+ virtual ~nsAbAddressCollector();
+ already_AddRefed<nsIAbCard> GetCardForAddress(const nsACString &aEmailAddress,
+ nsIAbDirectory **aDirectory);
+ void AutoCollectScreenName(nsIAbCard *aCard, const nsACString &aEmail);
+ bool SetNamesForCard(nsIAbCard *aSenderCard, const nsACString &aFullName);
+ void SplitFullName(const nsCString &aFullName, nsCString &aFirstName,
+ nsCString &aLastName);
+ void SetUpAbFromPrefs(nsIPrefBranch *aPrefBranch);
+ nsCOMPtr <nsIAbDirectory> mDirectory;
+ nsCString mABURI;
+};
+
+#endif // _nsAbAddressCollector_H_
+
diff --git a/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js b/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js
new file mode 100644
index 000000000..1ace4ca50
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function nsAbAutoCompleteMyDomain() {}
+
+nsAbAutoCompleteMyDomain.prototype = {
+ classID: Components.ID("{5b259db2-e451-4de9-8a6f-cfba91402973}"),
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.nsIAutoCompleteSearch]),
+
+ cachedIdKey: "",
+ cachedIdentity: null,
+
+ applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
+ startSearch: function(aString, aSearchParam, aResult, aListener) {
+ let params = aSearchParam ? JSON.parse(aSearchParam) : {};
+ let applicable = ("type" in params) && this.applicableHeaders.has(params.type);
+ const ACR = Components.interfaces.nsIAutoCompleteResult;
+ var address = null;
+ if (applicable && aString && !aString.includes(",")) {
+ if (("idKey" in params) && (params.idKey != this.cachedIdKey)) {
+ this.cachedIdentity = MailServices.accounts.getIdentity(params.idKey);
+ this.cachedIdKey = params.idKey;
+ }
+ if (this.cachedIdentity.autocompleteToMyDomain)
+ address = aString.includes("@") ? aString :
+ this.cachedIdentity.email.replace(/[^@]*/, aString);
+ }
+
+ var result = {
+ searchString: aString,
+ searchResult: address ? ACR.RESULT_SUCCESS : ACR.RESULT_FAILURE,
+ defaultIndex: -1,
+ errorDescription: null,
+ matchCount: address ? 1 : 0,
+ getValueAt: function() { return address; },
+ getLabelAt: function() { return this.getValueAt(); },
+ getCommentAt: function() { return null; },
+ getStyleAt: function() { return "default-match"; },
+ getImageAt: function() { return null; },
+ getFinalCompleteValueAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+ removeValueAt: function() {}
+ };
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch: function() {}
+};
+
+var components = [nsAbAutoCompleteMyDomain];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/addrbook/src/nsAbAutoCompleteSearch.js b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js
new file mode 100644
index 000000000..c6c9b8db8
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js
@@ -0,0 +1,466 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/ABQueryUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var ACR = Components.interfaces.nsIAutoCompleteResult;
+var nsIAbAutoCompleteResult = Components.interfaces.nsIAbAutoCompleteResult;
+
+function nsAbAutoCompleteResult(aSearchString) {
+ // Can't create this in the prototype as we'd get the same array for
+ // all instances
+ this._searchResults = []; // final results
+ this.searchString = aSearchString;
+ this._collectedValues = new Map(); // temporary unsorted results
+ // Get model query from pref; this will return mail.addr_book.autocompletequery.format.phonetic
+ // if mail.addr_book.show_phonetic_fields == true
+ this.modelQuery = getModelQuery("mail.addr_book.autocompletequery.format");
+ // check if the currently active model query has been modified by user
+ this._modelQueryHasUserValue = modelQueryHasUserValue("mail.addr_book.autocompletequery.format");
+}
+
+nsAbAutoCompleteResult.prototype = {
+ _searchResults: null,
+
+ // nsIAutoCompleteResult
+
+ modelQuery: null,
+ searchString: null,
+ searchResult: ACR.RESULT_NOMATCH,
+ defaultIndex: -1,
+ errorDescription: null,
+
+ get matchCount() {
+ return this._searchResults.length;
+ },
+
+ getValueAt: function getValueAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getLabelAt: function getLabelAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ getCommentAt: function getCommentAt(aIndex) {
+ return this._searchResults[aIndex].comment;
+ },
+
+ getStyleAt: function getStyleAt(aIndex) {
+ return "local-abook";
+ },
+
+ getImageAt: function getImageAt(aIndex) {
+ return "";
+ },
+
+ getFinalCompleteValueAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) {
+ },
+
+ // nsIAbAutoCompleteResult
+
+ getCardAt: function getCardAt(aIndex) {
+ return this._searchResults[aIndex].card;
+ },
+
+ getEmailToUse: function getEmailToUse(aIndex) {
+ return this._searchResults[aIndex].emailToUse;
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([ACR, nsIAbAutoCompleteResult])
+}
+
+function nsAbAutoCompleteSearch() {}
+
+nsAbAutoCompleteSearch.prototype = {
+ // For component registration
+ classID: Components.ID("2f946df9-114c-41fe-8899-81f10daf4f0c"),
+
+ // This is set from a preference,
+ // 0 = no comment column, 1 = name of address book this card came from
+ // Other numbers currently unused (hence default to zero)
+ _commentColumn: 0,
+ _parser: MailServices.headerParser,
+ _abManager: MailServices.ab,
+ applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
+ // Private methods
+
+ /**
+ * Returns the popularity index for a given card. This takes account of a
+ * translation bug whereby Thunderbird 2 stores its values in mork as
+ * hexadecimal, and Thunderbird 3 stores as decimal.
+ *
+ * @param aDirectory The directory that the card is in.
+ * @param aCard The card to return the popularity index for.
+ */
+ _getPopularityIndex: function _getPopularityIndex(aDirectory, aCard) {
+ let popularityValue = aCard.getProperty("PopularityIndex", "0");
+ let popularityIndex = parseInt(popularityValue);
+
+ // If we haven't parsed it the first time round, parse it as hexadecimal
+ // and repair so that we don't have to keep repairing.
+ if (isNaN(popularityIndex)) {
+ popularityIndex = parseInt(popularityValue, 16);
+
+ // If its still NaN, just give up, we shouldn't ever get here.
+ if (isNaN(popularityIndex))
+ popularityIndex = 0;
+
+ // Now store this change so that we're not changing it each time around.
+ if (!aDirectory.readOnly) {
+ aCard.setProperty("PopularityIndex", popularityIndex);
+ try {
+ aDirectory.modifyCard(aCard);
+ }
+ catch (ex) {
+ Components.utils.reportError(ex);
+ }
+ }
+ }
+ return popularityIndex;
+ },
+
+ /**
+ * Gets the score of the (full) address, given the search input. We want
+ * results that match the beginning of a "word" in the result to score better
+ * than a result that matches only in the middle of the word.
+ *
+ * @param aCard - the card whose score is being decided
+ * @param aAddress - full lower-cased address, including display name and address
+ * @param aSearchString - search string provided by user
+ * @return a score; a higher score is better than a lower one
+ */
+ _getScore: function(aCard, aAddress, aSearchString) {
+ const BEST = 100;
+
+ // We will firstly check if the search term provided by the user
+ // is the nick name for the card or at least in the beginning of it.
+ let nick = aCard.getProperty("NickName", "").toLocaleLowerCase();
+ aSearchString = aSearchString.toLocaleLowerCase();
+ if (nick == aSearchString)
+ return BEST + 1;
+ if (nick.indexOf(aSearchString) == 0)
+ return BEST;
+
+ // We'll do this case-insensitively and ignore the domain.
+ let atIdx = aAddress.lastIndexOf("@");
+ if (atIdx != -1) // mail lists don't have an @
+ aAddress = aAddress.substr(0, atIdx);
+ let idx = aAddress.indexOf(aSearchString);
+ if (idx == 0)
+ return BEST;
+ if (idx == -1)
+ return 0;
+
+ // We want to treat firstname, lastname and word boundary(ish) parts of
+ // the email address the same. E.g. for "John Doe (:xx) <jd.who@example.com>"
+ // all of these should score the same: "John", "Doe", "xx",
+ // ":xx", "jd", "who".
+ let prevCh = aAddress.charAt(idx - 1);
+ if (/[ :."'(\-_<&]/.test(prevCh))
+ return BEST;
+
+ // The match was inside a word -> we don't care about the position.
+ return 0;
+ },
+
+ /**
+ * Searches cards in the given directory. If a card is matched (and isn't
+ * a mailing list) then the function will add a result for each email address
+ * that exists.
+ *
+ * @param searchQuery The boolean search query to use.
+ * @param directory An nsIAbDirectory to search.
+ * @param result The result element to append results to.
+ */
+ _searchCards: function(searchQuery, directory, result) {
+ let childCards;
+ try {
+ childCards = this._abManager.getDirectory(directory.URI + searchQuery).childCards;
+ } catch (e) {
+ Components.utils.reportError("Error running addressbook query '" + searchQuery + "': " + e);
+ return;
+ }
+
+ // Cache this values to save going through xpconnect each time
+ var commentColumn = this._commentColumn == 1 ? directory.dirName : "";
+
+ // Now iterate through all the cards.
+ while (childCards.hasMoreElements()) {
+ var card = childCards.getNext();
+
+ if (card instanceof Components.interfaces.nsIAbCard) {
+ if (card.isMailList)
+ this._addToResult(commentColumn, directory, card, "", true, result);
+ else {
+ let email = card.primaryEmail;
+ if (email)
+ this._addToResult(commentColumn, directory, card, email, true, result);
+
+ email = card.getProperty("SecondEmail", "");
+ if (email)
+ this._addToResult(commentColumn, directory, card, email, false, result);
+ }
+ }
+ }
+ },
+
+ /**
+ * Checks the parent card and email address of an autocomplete results entry
+ * from a previous result against the search parameters to see if that entry
+ * should still be included in the narrowed-down result.
+ *
+ * @param aCard The card to check.
+ * @param aEmailToUse The email address to check against.
+ * @param aSearchWords Array of words in the multi word search string.
+ * @return True if the card matches the search parameters, false
+ * otherwise.
+ */
+ _checkEntry: function _checkEntry(aCard, aEmailToUse, aSearchWords) {
+ // Joining values of many fields in a single string so that a single
+ // search query can be fired on all of them at once. Separating them
+ // using spaces so that field1=> "abc" and field2=> "def" on joining
+ // shouldn't return true on search for "bcd".
+ // Note: This should be constructed from model query pref using
+ // getModelQuery("mail.addr_book.autocompletequery.format")
+ // but for now we hard-code the default value equivalent of the pref here
+ // or else bail out before and reconstruct the full c++ query if the pref
+ // has been customized (modelQueryHasUserValue), so that we won't get here.
+ let cumulativeFieldText = aCard.displayName + " " +
+ aCard.firstName + " " +
+ aCard.lastName + " " +
+ aEmailToUse + " " +
+ aCard.getProperty("NickName", "");
+ if (aCard.isMailList)
+ cumulativeFieldText += " " + aCard.getProperty("Notes", "");
+ cumulativeFieldText = cumulativeFieldText.toLocaleLowerCase();
+
+ return aSearchWords.every(String.prototype.includes,
+ cumulativeFieldText);
+ },
+
+ /**
+ * Checks to see if an emailAddress (name/address) is a duplicate of an
+ * existing entry already in the results. If the emailAddress is found, it
+ * will remove the existing element if the popularity of the new card is
+ * higher than the previous card.
+ *
+ * @param directory The directory that the card is in.
+ * @param card The card that could be a duplicate.
+ * @param lcEmailAddress The emailAddress (name/address combination) to check
+ * for duplicates against. Lowercased.
+ * @param currentResults The current results list.
+ */
+ _checkDuplicate: function (directory, card, lcEmailAddress, currentResults) {
+ let existingResult = currentResults._collectedValues.get(lcEmailAddress);
+ if (!existingResult)
+ return false;
+
+ let popIndex = this._getPopularityIndex(directory, card);
+ // It's a duplicate, is the new one more popular?
+ if (popIndex > existingResult.popularity) {
+ // Yes it is, so delete this element, return false and allow
+ // _addToResult to sort the new element into the correct place.
+ currentResults._collectedValues.delete(lcEmailAddress);
+ return false;
+ }
+ // Not more popular, but still a duplicate. Return true and _addToResult
+ // will just forget about it.
+ return true;
+ },
+
+ /**
+ * Adds a card to the results list if it isn't a duplicate. The function will
+ * order the results by popularity.
+ *
+ * @param commentColumn The text to be displayed in the comment column
+ * (if any).
+ * @param directory The directory that the card is in.
+ * @param card The card being added to the results.
+ * @param emailToUse The email address from the card that should be used
+ * for this result.
+ * @param isPrimaryEmail Is the emailToUse the primary email? Set to true if
+ * it is the case. For mailing lists set it to true.
+ * @param result The result to add the new entry to.
+ */
+ _addToResult: function(commentColumn, directory, card,
+ emailToUse, isPrimaryEmail, result) {
+ let mbox = this._parser.makeMailboxObject(card.displayName,
+ card.isMailList ? card.getProperty("Notes", "") || card.displayName :
+ emailToUse);
+ if (!mbox.email)
+ return;
+
+ let emailAddress = mbox.toString();
+ let lcEmailAddress = emailAddress.toLocaleLowerCase();
+
+ // If it is a duplicate, then just return and don't add it. The
+ // _checkDuplicate function deals with it all for us.
+ if (this._checkDuplicate(directory, card, lcEmailAddress, result))
+ return;
+
+ result._collectedValues.set(lcEmailAddress, {
+ value: emailAddress,
+ comment: commentColumn,
+ card: card,
+ isPrimaryEmail: isPrimaryEmail,
+ emailToUse: emailToUse,
+ popularity: this._getPopularityIndex(directory, card),
+ score: this._getScore(card, lcEmailAddress, result.searchString)
+ });
+ },
+
+ // nsIAutoCompleteSearch
+
+ /**
+ * Starts a search based on the given parameters.
+ *
+ * @see nsIAutoCompleteSearch for parameter details.
+ *
+ * It is expected that aSearchParam contains the identity (if any) to use
+ * for determining if an address book should be autocompleted against.
+ */
+ startSearch: function startSearch(aSearchString, aSearchParam,
+ aPreviousResult, aListener) {
+ let params = aSearchParam ? JSON.parse(aSearchParam) : {};
+ var result = new nsAbAutoCompleteResult(aSearchString);
+ if (("type" in params) && !this.applicableHeaders.has(params.type)) {
+ result.searchResult = ACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, result);
+ return;
+ }
+
+ let fullString = aSearchString && aSearchString.trim().toLocaleLowerCase();
+
+ // If the search string is empty, or contains a comma, or the user
+ // hasn't enabled autocomplete, then just return no matches or the
+ // result ignored.
+ // The comma check is so that we don't autocomplete against the user
+ // entering multiple addresses.
+ if (!fullString || aSearchString.includes(",")) {
+ result.searchResult = ACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, result);
+ return;
+ }
+
+ // Array of all the terms from the fullString search query
+ // (separated on the basis of spaces or exact terms on the
+ // basis of quotes).
+ let searchWords = getSearchTokens(fullString);
+
+ // Find out about the comment column
+ try {
+ this._commentColumn = Services.prefs.getIntPref("mail.autoComplete.commentColumn");
+ } catch(e) { }
+
+ if (aPreviousResult instanceof nsIAbAutoCompleteResult &&
+ aSearchString.startsWith(aPreviousResult.searchString) &&
+ aPreviousResult.searchResult == ACR.RESULT_SUCCESS &&
+ !result._modelQueryHasUserValue &&
+ result.modelQuery == aPreviousResult.modelQuery) {
+ // We have successful previous matches, and model query has not changed since
+ // previous search, therefore just iterate through the list of previous result
+ // entries and reduce as appropriate (via _checkEntry function).
+ // Test for model query change is required: when reverting back from custom to
+ // default query, result._modelQueryHasUserValue==false, but we must bail out.
+ // Todo: However, if autocomplete model query has been customized, we fall
+ // back to using the full query again instead of reducing result list in js;
+ // The full query might be less performant as it's fired against entire AB,
+ // so we should try morphing the query for js. We can't use the _checkEntry
+ // js query yet because it is hardcoded (mimic default model query).
+ // At least we now allow users to customize their autocomplete model query...
+ for (let i = 0; i < aPreviousResult.matchCount; ++i) {
+ let card = aPreviousResult.getCardAt(i);
+ let email = aPreviousResult.getEmailToUse(i);
+ if (this._checkEntry(card, email, searchWords)) {
+ // Add matches into the results array. We re-sort as needed later.
+ result._searchResults.push({
+ value: aPreviousResult.getValueAt(i),
+ comment: aPreviousResult.getCommentAt(i),
+ card: card,
+ isPrimaryEmail: (card.primaryEmail == email),
+ emailToUse: email,
+ popularity: parseInt(card.getProperty("PopularityIndex", "0")),
+ score: this._getScore(card,
+ aPreviousResult.getValueAt(i).toLocaleLowerCase(),
+ fullString)
+ });
+ }
+ }
+ }
+ else
+ {
+ // Construct the search query from pref; using a query means we can
+ // optimise on running the search through c++ which is better for string
+ // comparisons (_checkEntry is relatively slow).
+ // When user's fullstring search expression is a multiword query, search
+ // for each word separately so that each result contains all the words
+ // from the fullstring in the fields of the addressbook card
+ // (see bug 558931 for explanations).
+ // Use helper method to split up search query to multi-word search
+ // query against multiple fields.
+ let searchWords = getSearchTokens(fullString);
+ let searchQuery = generateQueryURI(result.modelQuery, searchWords);
+
+ // Now do the searching
+ let allABs = this._abManager.directories;
+
+ // We're not going to bother searching sub-directories, currently the
+ // architecture forces all cards that are in mailing lists to be in ABs as
+ // well, therefore by searching sub-directories (aka mailing lists) we're
+ // just going to find duplicates.
+ while (allABs.hasMoreElements()) {
+ let dir = allABs.getNext();
+ if (dir instanceof Components.interfaces.nsIAbDirectory &&
+ dir.useForAutocomplete(("idKey" in params) ? params.idKey : null)) {
+ this._searchCards(searchQuery, dir, result);
+ }
+ }
+
+ result._searchResults = [...result._collectedValues.values()];
+ }
+
+ // Sort the results. Scoring may have changed so do it even if this is
+ // just filtered previous results.
+ result._searchResults.sort(function(a, b) {
+ // Order by 1) descending score, then 2) descending popularity,
+ // then 3) primary email before secondary for the same card, then
+ // 4) by emails sorted alphabetically.
+ return (b.score - a.score) ||
+ (b.popularity - a.popularity) ||
+ ((a.card == b.card && a.isPrimaryEmail) ? -1 : 0) ||
+ a.value.localeCompare(b.value);
+ });
+
+ if (result.matchCount) {
+ result.searchResult = ACR.RESULT_SUCCESS;
+ result.defaultIndex = 0;
+ }
+
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch: function stopSearch() {
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces
+ .nsIAutoCompleteSearch])
+};
+
+// Module
+
+var components = [nsAbAutoCompleteSearch];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/addrbook/src/nsAbBSDirectory.cpp b/mailnews/addrbook/src/nsAbBSDirectory.cpp
new file mode 100644
index 000000000..0d018bbda
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBSDirectory.cpp
@@ -0,0 +1,323 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIPrefService.h"
+#include "nsAbBSDirectory.h"
+
+#include "nsDirPrefs.h"
+#include "nsAbBaseCID.h"
+#include "nsAddrDatabase.h"
+#include "nsIAbManager.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsServiceManagerUtils.h"
+#include "nsAbDirFactoryService.h"
+#include "nsAbMDBDirFactory.h"
+#include "nsArrayEnumerator.h"
+
+#include "nsCRTGlue.h"
+
+nsAbBSDirectory::nsAbBSDirectory()
+: mInitialized(false)
+, mServers(13)
+{
+}
+
+nsAbBSDirectory::~nsAbBSDirectory()
+{
+}
+
+NS_IMETHODIMP nsAbBSDirectory::Init(const char *aURI)
+{
+ mURI = aURI;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsAbBSDirectory, nsAbDirProperty)
+
+nsresult nsAbBSDirectory::CreateDirectoriesFromFactory(const nsACString &aURI,
+ DIR_Server *aServer,
+ bool aNotify)
+{
+ nsresult rv;
+
+ // Get the directory factory service
+ nsCOMPtr<nsIAbDirFactoryService> dirFactoryService =
+ do_GetService(NS_ABDIRFACTORYSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ // Get the directory factory from the URI
+ nsCOMPtr<nsIAbDirFactory> dirFactory;
+ rv = dirFactoryService->GetDirFactory(aURI, getter_AddRefs(dirFactory));
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ // Create the directories
+ nsCOMPtr<nsISimpleEnumerator> newDirEnumerator;
+ rv = dirFactory->GetDirectories(NS_ConvertUTF8toUTF16(aServer->description),
+ aURI,
+ nsDependentCString(aServer->prefName),
+ getter_AddRefs(newDirEnumerator));
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ // Enumerate through the directories adding them
+ // to the sub directories array
+ bool hasMore;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+
+ while (NS_SUCCEEDED(newDirEnumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> newDirSupports;
+ rv = newDirEnumerator->GetNext(getter_AddRefs(newDirSupports));
+ if(NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIAbDirectory> childDir = do_QueryInterface(newDirSupports, &rv);
+ if(NS_FAILED(rv))
+ continue;
+
+ // Define a relationship between the preference
+ // entry and the directory
+ mServers.Put(childDir, aServer);
+
+ mSubDirectories.AppendObject(childDir);
+
+ if (aNotify && abManager)
+ abManager->NotifyDirectoryItemAdded(this, childDir);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::GetChildNodes(nsISimpleEnumerator* *aResult)
+{
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewArrayEnumerator(aResult, mSubDirectories);
+}
+
+nsresult nsAbBSDirectory::EnsureInitialized()
+{
+ if (mInitialized)
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbDirFactoryService> dirFactoryService =
+ do_GetService(NS_ABDIRFACTORYSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ nsTArray<DIR_Server*> *directories = DIR_GetDirectories();
+ if (!directories)
+ return NS_ERROR_FAILURE;
+
+ int32_t count = directories->Length();
+ for (int32_t i = 0; i < count; i++)
+ {
+ DIR_Server *server = directories->ElementAt(i);
+
+ // if this is a 4.x, local .na2 addressbook (PABDirectory)
+ // we must skip it.
+ // mozilla can't handle 4.x .na2 addressbooks
+ // note, the filename might be na2 for 4.x LDAP directories
+ // (we used the .na2 file for replication), and we don't want to skip
+ // those. see bug #127007
+ uint32_t fileNameLen = strlen(server->fileName);
+ if (((fileNameLen > kABFileName_PreviousSuffixLen) &&
+ strcmp(server->fileName + fileNameLen - kABFileName_PreviousSuffixLen,
+ kABFileName_PreviousSuffix) == 0) &&
+ (server->dirType == PABDirectory))
+ continue;
+
+ // Set the uri property
+ nsAutoCString URI (server->uri);
+ // This is in case the uri is never set
+ // in the nsDirPref.cpp code.
+ if (!server->uri)
+ {
+ URI = NS_LITERAL_CSTRING(kMDBDirectoryRoot);
+ URI += nsDependentCString(server->fileName);
+ }
+
+ /*
+ * Check that we are not converting from a
+ * a 4.x address book file e.g. pab.na2
+ * check if the URI ends with ".na2"
+ */
+ if (StringEndsWith(URI, NS_LITERAL_CSTRING(kABFileName_PreviousSuffix)))
+ URI.Replace(kMDBDirectoryRootLen, URI.Length() - kMDBDirectoryRootLen, server->fileName);
+
+ // Create the directories
+ rv = CreateDirectoriesFromFactory(URI, server, false /* notify */);
+
+ // If we failed, this could be because something has set a pref for us
+ // which is now broke (e.g. no factory present). So just ignore this one
+ // and move on.
+ if (NS_FAILED(rv))
+ NS_WARNING("CreateDirectoriesFromFactory failed - Invalid factory?");
+ }
+
+ mInitialized = true;
+ // sort directories by position...
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::CreateNewDirectory(const nsAString &aDirName,
+ const nsACString &aURI,
+ uint32_t aType,
+ const nsACString &aPrefName,
+ nsACString &aResult)
+{
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /*
+ * TODO
+ * This procedure is still MDB specific
+ * due to the dependence on the current
+ * nsDirPref.cpp code
+ */
+
+ nsCString URI(aURI);
+
+ /*
+ * The creation of the address book in the preferences
+ * is very MDB implementation specific.
+ * If the fileName attribute is null then it will
+ * create an appropriate file name.
+ * Somehow have to resolve this issue so that it
+ * is more general.
+ *
+ */
+ DIR_Server* server = nullptr;
+ rv = DIR_AddNewAddressBook(aDirName, EmptyCString(), URI,
+ (DirectoryType)aType, aPrefName, &server);
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ if (aType == PABDirectory) {
+ // Add the URI property
+ URI.AssignLiteral(kMDBDirectoryRoot);
+ URI.Append(nsDependentCString(server->fileName));
+ }
+
+ aResult.Assign(server->prefName);
+
+ rv = CreateDirectoriesFromFactory(URI, server, true /* notify */);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::CreateDirectoryByURI(const nsAString &aDisplayName,
+ const nsACString &aURI)
+{
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString fileName;
+ if (StringBeginsWith(aURI, NS_LITERAL_CSTRING(kMDBDirectoryRoot)))
+ fileName = Substring(aURI, kMDBDirectoryRootLen);
+
+ DIR_Server * server = nullptr;
+ rv = DIR_AddNewAddressBook(aDisplayName, fileName, aURI,
+ PABDirectory, EmptyCString(), &server);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = CreateDirectoriesFromFactory(aURI, server, true /* notify */);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::DeleteDirectory(nsIAbDirectory *directory)
+{
+ NS_ENSURE_ARG_POINTER(directory);
+
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DIR_Server *server = nullptr;
+ mServers.Get(directory, &server);
+
+ if (!server)
+ return NS_ERROR_FAILURE;
+
+ struct GetDirectories
+ {
+ GetDirectories(DIR_Server* aServer) : mServer(aServer) { }
+
+ nsCOMArray<nsIAbDirectory> directories;
+ DIR_Server* mServer;
+ };
+ GetDirectories getDirectories(server);
+ for (auto iter = mServers.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.UserData() == getDirectories.mServer) {
+ nsCOMPtr<nsIAbDirectory> abDir = do_QueryInterface(iter.Key());
+ getDirectories.directories.AppendObject(abDir);
+ }
+ }
+
+ DIR_DeleteServerFromList(server);
+
+ nsCOMPtr<nsIAbDirFactoryService> dirFactoryService =
+ do_GetService(NS_ABDIRFACTORYSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ uint32_t count = getDirectories.directories.Count();
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID);
+
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbDirectory> d = getDirectories.directories[i];
+
+ mServers.Remove(d);
+ mSubDirectories.RemoveObject(d);
+
+ if (abManager)
+ abManager->NotifyDirectoryDeleted(this, d);
+
+ nsCString uri;
+ rv = d->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirFactory> dirFactory;
+ rv = dirFactoryService->GetDirFactory(uri, getter_AddRefs(dirFactory));
+ if (NS_FAILED(rv))
+ continue;
+
+ rv = dirFactory->DeleteDirectory(d);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::HasDirectory(nsIAbDirectory *dir, bool *hasDir)
+{
+ if (!hasDir)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DIR_Server *dirServer = nullptr;
+ mServers.Get(dir, &dirServer);
+ return DIR_ContainsServer(dirServer, hasDir);
+}
+
+NS_IMETHODIMP nsAbBSDirectory::UseForAutocomplete(const nsACString &aIdentityKey,
+ bool *aResult)
+{
+ // For the "root" directory (kAllDirectoryRoot) always return true so that
+ // we can search sub directories that may or may not be local.
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
diff --git a/mailnews/addrbook/src/nsAbBSDirectory.h b/mailnews/addrbook/src/nsAbBSDirectory.h
new file mode 100644
index 000000000..bc550dbf5
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBSDirectory.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsAbBSDirectory_h__
+#define nsAbBSDirectory_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbDirProperty.h"
+
+#include "nsDataHashtable.h"
+#include "nsCOMArray.h"
+
+class nsAbBSDirectory : public nsAbDirProperty
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsAbBSDirectory();
+
+ // nsIAbDirectory methods
+ NS_IMETHOD Init(const char *aURI) override;
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD CreateNewDirectory(const nsAString &aDirName,
+ const nsACString &aURI,
+ uint32_t aType,
+ const nsACString &aPrefName,
+ nsACString &aResult) override;
+ NS_IMETHOD CreateDirectoryByURI(const nsAString &aDisplayName,
+ const nsACString &aURI) override;
+ NS_IMETHOD DeleteDirectory(nsIAbDirectory *directory) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory *dir, bool *hasDir) override;
+ NS_IMETHOD UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) override;
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+
+protected:
+ virtual ~nsAbBSDirectory();
+ nsresult EnsureInitialized();
+ nsresult CreateDirectoriesFromFactory(const nsACString &aURI,
+ DIR_Server* aServer, bool aNotify);
+
+protected:
+ bool mInitialized;
+ nsCOMArray<nsIAbDirectory> mSubDirectories;
+ nsDataHashtable<nsISupportsHashKey, DIR_Server*> mServers;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp
new file mode 100644
index 000000000..679ee792d
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsAbBoolExprToLDAPFilter.h"
+#include "nsStringGlue.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+const int nsAbBoolExprToLDAPFilter::TRANSLATE_CARD_PROPERTY = 1 << 0 ;
+const int nsAbBoolExprToLDAPFilter::ALLOW_NON_CONVERTABLE_CARD_PROPERTY = 1 << 1 ;
+
+nsresult nsAbBoolExprToLDAPFilter::Convert (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanExpression* expression,
+ nsCString& filter,
+ int flags)
+{
+ nsCString f;
+ nsresult rv = FilterExpression (map, expression, f, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filter = f;
+ return rv;
+}
+
+nsresult nsAbBoolExprToLDAPFilter::FilterExpression (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanExpression* expression,
+ nsCString& filter,
+ int flags)
+{
+ nsCOMPtr<nsIArray> childExpressions;
+ nsresult rv = expression->GetExpressions(getter_AddRefs(childExpressions));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ rv = childExpressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count == 0)
+ return NS_OK;
+
+ nsAbBooleanOperationType operation;
+ rv = expression->GetOperation(&operation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /*
+ * 3rd party query integration with Mozilla is achieved
+ * by calling nsAbLDAPDirectoryQuery::DoQuery(). Thus
+ * we can arrive here with a query asking for all the
+ * ldap attributes using the card:nsIAbCard interface.
+ *
+ * So we need to check that we are not creating a condition
+ * filter against this expression otherwise we will end up with an invalid
+ * filter equal to "(|)".
+ */
+
+ if (count == 1 )
+ {
+ nsCOMPtr<nsIAbBooleanConditionString>
+ childCondition(do_QueryElementAt(childExpressions, 1, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString name;
+ rv = childCondition->GetName (getter_Copies (name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if(name.Equals("card:nsIAbCard"))
+ return NS_OK;
+ }
+ }
+
+ filter.AppendLiteral("(");
+ switch (operation)
+ {
+ case nsIAbBooleanOperationTypes::AND:
+ filter.AppendLiteral("&");
+ rv = FilterExpressions (map, childExpressions, filter, flags);
+ break;
+ case nsIAbBooleanOperationTypes::OR:
+ filter.AppendLiteral("|");
+ rv = FilterExpressions (map, childExpressions, filter, flags);
+ break;
+ case nsIAbBooleanOperationTypes::NOT:
+ if (count > 1)
+ return NS_ERROR_FAILURE;
+ filter.AppendLiteral("!");
+ rv = FilterExpressions (map, childExpressions, filter, flags);
+ break;
+ default:
+ break;
+ }
+ filter.AppendLiteral(")");
+
+ return rv;
+}
+
+nsresult nsAbBoolExprToLDAPFilter::FilterExpressions (
+ nsIAbLDAPAttributeMap *map,
+ nsIArray* expressions,
+ nsCString& filter,
+ int flags)
+{
+ uint32_t count;
+ nsresult rv = expressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanConditionString> childCondition;
+ nsCOMPtr<nsIAbBooleanExpression> childExpression;
+ for (uint32_t i = 0; i < count; i++)
+ {
+ childCondition = do_QueryElementAt(expressions, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = FilterCondition (map, childCondition, filter, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+
+ childExpression = do_QueryElementAt(expressions, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = FilterExpression (map, childExpression, filter, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsAbBoolExprToLDAPFilter::FilterCondition (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanConditionString* condition,
+ nsCString& filter,
+ int flags)
+{
+ nsCString name;
+ nsresult rv = condition->GetName(getter_Copies (name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ldapAttr(name);
+ if (flags & TRANSLATE_CARD_PROPERTY)
+ {
+ rv = map->GetFirstAttribute (name, ldapAttr);
+ if (!(flags & ALLOW_NON_CONVERTABLE_CARD_PROPERTY) &&
+ !ATTRMAP_FOUND_ATTR(rv, ldapAttr))
+ return NS_OK;
+ }
+
+ nsAbBooleanConditionType conditionType;
+ rv = condition->GetCondition(&conditionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString value;
+ rv = condition->GetValue (getter_Copies (value));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ConvertUTF16toUTF8 vUTF8 (value);
+
+ switch (conditionType)
+ {
+ case nsIAbBooleanConditionTypes::DoesNotExist:
+ filter.AppendLiteral("(!(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=*))");
+ break;
+ case nsIAbBooleanConditionTypes::Exists:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=*)");
+ break;
+ case nsIAbBooleanConditionTypes::Contains:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.Append("=*");
+ filter.Append(vUTF8);
+ filter.AppendLiteral("*)");
+ break;
+ case nsIAbBooleanConditionTypes::DoesNotContain:
+ filter.AppendLiteral("(!(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=*");
+ filter.Append(vUTF8);
+ filter.AppendLiteral("*))");
+ break;
+ case nsIAbBooleanConditionTypes::Is:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::IsNot:
+ filter.AppendLiteral("(!(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral("))");
+ break;
+ case nsIAbBooleanConditionTypes::BeginsWith:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral("*)");
+ break;
+ case nsIAbBooleanConditionTypes::EndsWith:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=*");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::LessThan:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("<=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::GreaterThan:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral(">=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::SoundsLike:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("~=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::RegExp:
+ break;
+ default:
+ break;
+ }
+
+ return rv;
+}
+
diff --git a/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h
new file mode 100644
index 000000000..5ea892595
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBooleanExpressionToLDAPFilter_h__
+#define nsBooleanExpressionToLDAPFilter_h__
+
+#include "nsIAbBooleanExpression.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+class nsIAbLDAPAttributeMap;
+
+class nsAbBoolExprToLDAPFilter
+{
+public:
+ static const int TRANSLATE_CARD_PROPERTY ;
+ static const int ALLOW_NON_CONVERTABLE_CARD_PROPERTY ;
+
+ static nsresult Convert (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanExpression* expression,
+ nsCString& filter,
+ int flags = TRANSLATE_CARD_PROPERTY);
+
+protected:
+ static nsresult FilterExpression (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanExpression* expression,
+ nsCString& filter,
+ int flags);
+ static nsresult FilterExpressions (
+ nsIAbLDAPAttributeMap* map,
+ nsIArray* expressions,
+ nsCString& filter,
+ int flags);
+ static nsresult FilterCondition (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanConditionString* condition,
+ nsCString& filter,
+ int flags);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbBooleanExpression.cpp b/mailnews/addrbook/src/nsAbBooleanExpression.cpp
new file mode 100644
index 000000000..a1a39c1fa
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBooleanExpression.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbBooleanExpression.h"
+#include "nsComponentManagerUtils.h"
+
+NS_IMPL_ISUPPORTS(nsAbBooleanConditionString, nsIAbBooleanConditionString)
+
+nsAbBooleanConditionString::nsAbBooleanConditionString() :
+ mCondition (nsIAbBooleanConditionTypes::Exists)
+{
+}
+
+nsAbBooleanConditionString::~nsAbBooleanConditionString()
+{
+}
+
+/* attribute nsAbBooleanConditionType condition; */
+NS_IMETHODIMP nsAbBooleanConditionString::GetCondition(nsAbBooleanConditionType *aCondition)
+{
+ if (!aCondition)
+ return NS_ERROR_NULL_POINTER;
+
+ *aCondition = mCondition;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanConditionString::SetCondition(nsAbBooleanConditionType aCondition)
+{
+ mCondition = aCondition;
+
+ return NS_OK;
+}
+
+/* attribute string name; */
+NS_IMETHODIMP nsAbBooleanConditionString::GetName(char** aName)
+{
+ if (!aName)
+ return NS_ERROR_NULL_POINTER;
+
+ *aName = mName.IsEmpty() ? 0 : ToNewCString(mName);
+
+ return NS_OK;
+
+}
+NS_IMETHODIMP nsAbBooleanConditionString::SetName(const char* aName)
+{
+ if (!aName)
+ return NS_ERROR_NULL_POINTER;
+
+ mName = aName;
+
+ return NS_OK;
+}
+
+/* attribute wstring value; */
+NS_IMETHODIMP nsAbBooleanConditionString::GetValue(char16_t** aValue)
+{
+ if (!aValue)
+ return NS_ERROR_NULL_POINTER;
+
+ *aValue = ToNewUnicode(mValue);
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanConditionString::SetValue(const char16_t * aValue)
+{
+ if (!aValue)
+ return NS_ERROR_NULL_POINTER;
+
+ mValue = aValue;
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsAbBooleanExpression, nsIAbBooleanExpression)
+
+nsAbBooleanExpression::nsAbBooleanExpression() :
+ mOperation (nsIAbBooleanOperationTypes::AND)
+{
+}
+
+nsAbBooleanExpression::~nsAbBooleanExpression()
+{
+}
+
+/* attribute nsAbBooleanOperationType operation; */
+NS_IMETHODIMP nsAbBooleanExpression::GetOperation(nsAbBooleanOperationType *aOperation)
+{
+ if (!aOperation)
+ return NS_ERROR_NULL_POINTER;
+
+ *aOperation = mOperation;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanExpression::SetOperation(nsAbBooleanOperationType aOperation)
+{
+ mOperation = aOperation;
+
+ return NS_OK;
+}
+
+/* attribute nsIArray expressions; */
+NS_IMETHODIMP nsAbBooleanExpression::GetExpressions(nsIArray **aExpressions)
+{
+ if (!aExpressions)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!mExpressions)
+ {
+ mExpressions = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ if (!mExpressions)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ADDREF(*aExpressions = mExpressions);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbBooleanExpression::SetExpressions(nsIArray *aExpressions)
+{
+ if (!aExpressions)
+ return NS_ERROR_NULL_POINTER;
+
+ mExpressions = aExpressions;
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbBooleanExpression.h b/mailnews/addrbook/src/nsAbBooleanExpression.h
new file mode 100644
index 000000000..697caf5f5
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBooleanExpression.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbBooleanExpression_h__
+#define nsAbBooleanExpression_h__
+
+#include "nsIAbBooleanExpression.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIArray.h"
+
+class nsAbBooleanConditionString : public nsIAbBooleanConditionString
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABBOOLEANCONDITIONSTRING
+
+ nsAbBooleanConditionString();
+
+protected:
+ virtual ~nsAbBooleanConditionString();
+ nsAbBooleanConditionType mCondition;
+ nsCString mName;
+ nsString mValue;
+};
+
+class nsAbBooleanExpression: public nsIAbBooleanExpression
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABBOOLEANEXPRESSION
+
+ nsAbBooleanExpression();
+
+protected:
+ virtual ~nsAbBooleanExpression();
+ nsAbBooleanOperationType mOperation;
+ nsCOMPtr<nsIArray> mExpressions;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbCardProperty.cpp b/mailnews/addrbook/src/nsAbCardProperty.cpp
new file mode 100644
index 000000000..2c40a4034
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbCardProperty.cpp
@@ -0,0 +1,1193 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbCardProperty.h"
+#include "nsAbBaseCID.h"
+#include "nsIPrefService.h"
+#include "nsIAddrDatabase.h"
+#include "plbase64.h"
+#include "nsIStringBundle.h"
+#include "plstr.h"
+#include "nsMsgUtils.h"
+#include "nsINetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsVCardObj.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsIAbManager.h"
+
+#include "nsVariant.h"
+#include "nsIProperty.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "prmem.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+using namespace mozilla;
+
+#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst"
+
+const char sAddrbookProperties[] = "chrome://messenger/locale/addressbook/addressBook.properties";
+
+enum EAppendType {
+ eAppendLine,
+ eAppendLabel,
+ eAppendCityStateZip
+};
+
+struct AppendItem {
+ const char *mColumn;
+ const char* mLabel;
+ EAppendType mAppendType;
+};
+
+static const AppendItem NAME_ATTRS_ARRAY[] = {
+ {kDisplayNameProperty, "propertyDisplayName", eAppendLabel},
+ {kNicknameProperty, "propertyNickname", eAppendLabel},
+ {kPriEmailProperty, "", eAppendLine},
+#ifndef MOZ_THUNDERBIRD
+ {k2ndEmailProperty, "", eAppendLine},
+ {kScreenNameProperty, "propertyScreenName", eAppendLabel}
+#else
+ {k2ndEmailProperty, "", eAppendLine}
+#endif
+};
+
+static const AppendItem PHONE_ATTRS_ARRAY[] = {
+ {kWorkPhoneProperty, "propertyWork", eAppendLabel},
+ {kHomePhoneProperty, "propertyHome", eAppendLabel},
+ {kFaxProperty, "propertyFax", eAppendLabel},
+ {kPagerProperty, "propertyPager", eAppendLabel},
+ {kCellularProperty, "propertyCellular", eAppendLabel}
+};
+
+static const AppendItem HOME_ATTRS_ARRAY[] = {
+ {kHomeAddressProperty, "", eAppendLine},
+ {kHomeAddress2Property, "", eAppendLine},
+ {kHomeCityProperty, "", eAppendCityStateZip},
+ {kHomeCountryProperty, "", eAppendLine},
+ {kHomeWebPageProperty, "", eAppendLine}
+};
+
+static const AppendItem WORK_ATTRS_ARRAY[] = {
+ {kJobTitleProperty, "", eAppendLine},
+ {kDepartmentProperty, "", eAppendLine},
+ {kCompanyProperty, "", eAppendLine},
+ {kWorkAddressProperty, "", eAppendLine},
+ {kWorkAddress2Property, "", eAppendLine},
+ {kWorkCityProperty, "", eAppendCityStateZip},
+ {kWorkCountryProperty, "", eAppendLine},
+ {kWorkWebPageProperty, "", eAppendLine}
+};
+
+static const AppendItem CUSTOM_ATTRS_ARRAY[] = {
+ {kCustom1Property, "propertyCustom1", eAppendLabel},
+ {kCustom2Property, "propertyCustom2", eAppendLabel},
+ {kCustom3Property, "propertyCustom3", eAppendLabel},
+ {kCustom4Property, "propertyCustom4", eAppendLabel},
+ {kNotesProperty, "", eAppendLine}
+};
+
+#ifdef MOZ_THUNDERBIRD
+
+static const AppendItem CHAT_ATTRS_ARRAY[] = {
+ {kGtalkProperty, "propertyGtalk", eAppendLabel},
+ {kAIMProperty, "propertyAIM", eAppendLabel},
+ {kYahooProperty, "propertyYahoo", eAppendLabel},
+ {kSkypeProperty, "propertySkype", eAppendLabel},
+ {kQQProperty, "propertyQQ", eAppendLabel},
+ {kMSNProperty, "propertyMSN", eAppendLabel},
+ {kICQProperty, "propertyICQ", eAppendLabel},
+ {kXMPPProperty, "propertyXMPP", eAppendLabel},
+ {kIRCProperty, "propertyIRC", eAppendLabel}
+};
+#endif
+
+nsAbCardProperty::nsAbCardProperty()
+ : m_IsMailList(false)
+{
+ // Initialize some default properties
+ SetPropertyAsUint32(kPreferMailFormatProperty, nsIAbPreferMailFormat::unknown);
+ SetPropertyAsUint32(kPopularityIndexProperty, 0);
+ // Uninitialized...
+ SetPropertyAsUint32(kLastModifiedDateProperty, 0);
+}
+
+nsAbCardProperty::~nsAbCardProperty(void)
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAbCardProperty, nsIAbCard, nsIAbItem)
+
+NS_IMETHODIMP nsAbCardProperty::GetUuid(nsACString &uuid)
+{
+ // If we have indeterminate sub-ids, return an empty uuid.
+ if (m_directoryId.Equals("") || m_localId.Equals(""))
+ {
+ uuid.Truncate();
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> manager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return manager->GenerateUUID(m_directoryId, m_localId, uuid);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetDirectoryId(nsACString &dirId)
+{
+ dirId = m_directoryId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetDirectoryId(const nsACString &aDirId)
+{
+ m_directoryId = aDirId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetLocalId(nsACString &localId)
+{
+ localId = m_localId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetLocalId(const nsACString &aLocalId)
+{
+ m_localId = aLocalId;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsAbCardProperty::GetIsMailList(bool *aIsMailList)
+{
+ *aIsMailList = m_IsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetIsMailList(bool aIsMailList)
+{
+ m_IsMailList = aIsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetMailListURI(char **aMailListURI)
+{
+ if (aMailListURI)
+ {
+ *aMailListURI = ToNewCString(m_MailListURI);
+ return (*aMailListURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetMailListURI(const char *aMailListURI)
+{
+ if (aMailListURI)
+ {
+ m_MailListURI = aMailListURI;
+ return NS_OK;
+ }
+ else
+ return NS_ERROR_NULL_POINTER;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Property bag portion of nsAbCardProperty
+///////////////////////////////////////////////////////////////////////////////
+
+class nsAbSimpleProperty final : public nsIProperty {
+public:
+ nsAbSimpleProperty(const nsACString& aName, nsIVariant* aValue)
+ : mName(aName), mValue(aValue)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROPERTY
+protected:
+ ~nsAbSimpleProperty() {}
+ nsCString mName;
+ nsCOMPtr<nsIVariant> mValue;
+};
+
+NS_IMPL_ISUPPORTS(nsAbSimpleProperty, nsIProperty)
+
+NS_IMETHODIMP
+nsAbSimpleProperty::GetName(nsAString& aName)
+{
+ aName.Assign(NS_ConvertUTF8toUTF16(mName));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbSimpleProperty::GetValue(nsIVariant* *aValue)
+{
+ NS_IF_ADDREF(*aValue = mValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetProperties(nsISimpleEnumerator **props)
+{
+ nsCOMArray<nsIProperty> propertyArray(m_properties.Count());
+ for (auto iter = m_properties.Iter(); !iter.Done(); iter.Next()) {
+ propertyArray.AppendObject(new nsAbSimpleProperty(iter.Key(),
+ iter.UserData()));
+ }
+ return NS_NewArrayEnumerator(props, propertyArray);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetProperty(const nsACString &name,
+ nsIVariant *defaultValue,
+ nsIVariant **value)
+{
+ if (!m_properties.Get(name, value))
+ NS_ADDREF(*value = defaultValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsAString(const char *name, nsAString &value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ?
+ variant->GetAsAString(value) : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsAUTF8String(const char *name, nsACString &value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ?
+ variant->GetAsAUTF8String(value) : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsUint32(const char *name, uint32_t *value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ?
+ variant->GetAsUint32(value) : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsBool(const char *name, bool *value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ?
+ variant->GetAsBool(value) : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetProperty(const nsACString &name, nsIVariant *value)
+{
+ m_properties.Put(name, value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsAString(const char *name, const nsAString &value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsAString(value);
+ m_properties.Put(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsAUTF8String(const char *name, const nsACString &value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsAUTF8String(value);
+ m_properties.Put(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsUint32(const char *name, uint32_t value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsUint32(value);
+ m_properties.Put(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsBool(const char *name, bool value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsBool(value);
+ m_properties.Put(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::DeleteProperty(const nsACString &name)
+{
+ m_properties.Remove(name);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetFirstName(nsAString &aString)
+{
+ nsresult rv = GetPropertyAsAString(kFirstNameProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetFirstName(const nsAString &aString)
+{
+ return SetPropertyAsAString(kFirstNameProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetLastName(nsAString &aString)
+{
+ nsresult rv = GetPropertyAsAString(kLastNameProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetLastName(const nsAString &aString)
+{
+ return SetPropertyAsAString(kLastNameProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetDisplayName(nsAString &aString)
+{
+ nsresult rv = GetPropertyAsAString(kDisplayNameProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetDisplayName(const nsAString &aString)
+{
+ return SetPropertyAsAString(kDisplayNameProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPrimaryEmail(nsAString &aString)
+{
+ nsresult rv = GetPropertyAsAString(kPriEmailProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPrimaryEmail(const nsAString &aString)
+{
+ return SetPropertyAsAString(kPriEmailProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::HasEmailAddress(const nsACString &aEmailAddress,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+
+ nsCString emailAddress;
+ nsresult rv = GetPropertyAsAUTF8String(kPriEmailProperty, emailAddress);
+ if (rv != NS_ERROR_NOT_AVAILABLE &&
+ emailAddress.Equals(aEmailAddress, nsCaseInsensitiveCStringComparator()))
+ {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ rv = GetPropertyAsAUTF8String(k2ndEmailProperty, emailAddress);
+ if (rv != NS_ERROR_NOT_AVAILABLE &&
+ emailAddress.Equals(aEmailAddress, nsCaseInsensitiveCStringComparator()))
+ *aResult = true;
+
+ return NS_OK;
+}
+
+// This function may be overridden by derived classes for
+// nsAb*Card specific implementations.
+NS_IMETHODIMP nsAbCardProperty::Copy(nsIAbCard* srcCard)
+{
+ NS_ENSURE_ARG_POINTER(srcCard);
+
+ nsCOMPtr<nsISimpleEnumerator> properties;
+ nsresult rv = srcCard->GetProperties(getter_AddRefs(properties));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ nsCOMPtr<nsISupports> result;
+ while (NS_SUCCEEDED(rv = properties->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = properties->GetNext(getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIProperty> property = do_QueryInterface(result, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString name;
+ property->GetName(name);
+ nsCOMPtr<nsIVariant> value;
+ property->GetValue(getter_AddRefs(value));
+
+ SetProperty(NS_ConvertUTF16toUTF8(name), value);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isMailList;
+ srcCard->GetIsMailList(&isMailList);
+ SetIsMailList(isMailList);
+
+ nsCString mailListURI;
+ srcCard->GetMailListURI(getter_Copies(mailListURI));
+ SetMailListURI(mailListURI.get());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::Equals(nsIAbCard *card, bool *result)
+{
+ *result = (card == this);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The following methods are other views of a card
+////////////////////////////////////////////////////////////////////////////////
+
+// XXX: Use the category manager instead of this file to implement these
+NS_IMETHODIMP nsAbCardProperty::TranslateTo(const nsACString &type, nsACString &result)
+{
+ if (type.EqualsLiteral("base64xml"))
+ return ConvertToBase64EncodedXML(result);
+ else if (type.EqualsLiteral("xml"))
+ {
+ nsString utf16String;
+ nsresult rv = ConvertToXMLPrintData(utf16String);
+ NS_ENSURE_SUCCESS(rv, rv);
+ result = NS_ConvertUTF16toUTF8(utf16String);
+ return NS_OK;
+ }
+ else if (type.EqualsLiteral("vcard"))
+ return ConvertToEscapedVCard(result);
+
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+//
+static VObject* myAddPropValue(VObject *o, const char *propName, const char16_t *propValue, bool *aCardHasData)
+{
+ if (aCardHasData)
+ *aCardHasData = true;
+ return addPropValue(o, propName, NS_ConvertUTF16toUTF8(propValue).get());
+}
+
+nsresult nsAbCardProperty::ConvertToEscapedVCard(nsACString &aResult)
+{
+ nsString str;
+ nsresult rv;
+ bool vCardHasData = false;
+ VObject* vObj = newVObject(VCCardProp);
+ VObject* t;
+
+ // [comment from 4.x]
+ // Big flame coming....so Vobject is not designed at all to work with an array of
+ // attribute values. It wants you to have all of the attributes easily available. You
+ // cannot add one attribute at a time as you find them to the vobject. Why? Because
+ // it creates a property for a particular type like phone number and then that property
+ // has multiple values. This implementation is not pretty. I can hear my algos prof
+ // yelling from here.....I have to do a linear search through my attributes array for
+ // EACH vcard property we want to set. *sigh* One day I will have time to come back
+ // to this function and remedy this O(m*n) function where n = # attribute values and
+ // m = # of vcard properties....
+
+ (void)GetDisplayName(str);
+ if (!str.IsEmpty()) {
+ myAddPropValue(vObj, VCFullNameProp, str.get(), &vCardHasData);
+ }
+
+ (void)GetLastName(str);
+ if (!str.IsEmpty()) {
+ t = isAPropertyOf(vObj, VCNameProp);
+ if (!t)
+ t = addProp(vObj, VCNameProp);
+ myAddPropValue(t, VCFamilyNameProp, str.get(), &vCardHasData);
+ }
+
+ (void)GetFirstName(str);
+ if (!str.IsEmpty()) {
+ t = isAPropertyOf(vObj, VCNameProp);
+ if (!t)
+ t = addProp(vObj, VCNameProp);
+ myAddPropValue(t, VCGivenNameProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kCompanyProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCOrgProp);
+ if (!t)
+ t = addProp(vObj, VCOrgProp);
+ myAddPropValue(t, VCOrgNameProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kDepartmentProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCOrgProp);
+ if (!t)
+ t = addProp(vObj, VCOrgProp);
+ myAddPropValue(t, VCOrgUnitProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkAddress2Property, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCPostalBoxProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkAddressProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCStreetAddressProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkCityProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCCityProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkStateProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCRegionProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkZipCodeProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCPostalCodeProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkCountryProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCCountryNameProp, str.get(), &vCardHasData);
+ }
+ else
+ {
+ // only add this if VCAdrProp already exists
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (t)
+ {
+ addProp(t, VCDomesticProp);
+ }
+ }
+
+ (void)GetPrimaryEmail(str);
+ if (!str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCEmailAddressProp, str.get(), &vCardHasData);
+ addProp(t, VCInternetProp);
+ }
+
+ rv = GetPropertyAsAString(kJobTitleProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ myAddPropValue(vObj, VCTitleProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkPhoneProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCWorkProp);
+ }
+
+ rv = GetPropertyAsAString(kFaxProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCFaxProp);
+ }
+
+ rv = GetPropertyAsAString(kPagerProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCPagerProp);
+ }
+
+ rv = GetPropertyAsAString(kHomePhoneProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCHomeProp);
+ }
+
+ rv = GetPropertyAsAString(kCellularProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCCellularProp);
+ }
+
+ rv = GetPropertyAsAString(kNotesProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ myAddPropValue(vObj, VCNoteProp, str.get(), &vCardHasData);
+ }
+
+ uint32_t format;
+ rv = GetPropertyAsUint32(kPreferMailFormatProperty, &format);
+ if (NS_SUCCEEDED(rv) && format == nsIAbPreferMailFormat::html) {
+ myAddPropValue(vObj, VCUseHTML, u"TRUE", &vCardHasData);
+ }
+ else if (NS_SUCCEEDED(rv) && format == nsIAbPreferMailFormat::plaintext) {
+ myAddPropValue(vObj, VCUseHTML, u"FALSE", &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkWebPageProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ myAddPropValue(vObj, VCURLProp, str.get(), &vCardHasData);
+ }
+
+ myAddPropValue(vObj, VCVersionProp, u"2.1", nullptr);
+
+ if (!vCardHasData) {
+ aResult.Truncate();
+ cleanVObject(vObj);
+ return NS_OK;
+ }
+
+ int len = 0;
+ char *vCard = writeMemVObject(0, &len, vObj);
+ if (vObj)
+ cleanVObject(vObj);
+
+ nsCString escResult;
+ MsgEscapeString(nsDependentCString(vCard), nsINetUtil::ESCAPE_URL_PATH, escResult);
+ aResult = escResult;
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::ConvertToBase64EncodedXML(nsACString &result)
+{
+ nsresult rv;
+ nsString xmlStr;
+
+ xmlStr.AppendLiteral("<?xml version=\"1.0\"?>\n"
+ "<?xml-stylesheet type=\"text/css\" href=\"chrome://messagebody/content/addressbook/print.css\"?>\n"
+ "<directory>\n");
+
+ // Get Address Book string and set it as title of XML document
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ if (stringBundleService) {
+ rv = stringBundleService->CreateBundle(sAddrbookProperties, getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsString addrBook;
+ rv = bundle->GetStringFromName(u"addressBook", getter_Copies(addrBook));
+ if (NS_SUCCEEDED(rv)) {
+ xmlStr.AppendLiteral("<title xmlns=\"http://www.w3.org/1999/xhtml\">");
+ xmlStr.Append(addrBook);
+ xmlStr.AppendLiteral("</title>\n");
+ }
+ }
+ }
+
+ nsString xmlSubstr;
+ rv = ConvertToXMLPrintData(xmlSubstr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ xmlStr.Append(xmlSubstr);
+ xmlStr.AppendLiteral("</directory>\n");
+
+ char *tmpRes = PL_Base64Encode(NS_ConvertUTF16toUTF8(xmlStr).get(), 0, nullptr);
+ result.Assign(tmpRes);
+ PR_Free(tmpRes);
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::ConvertToXMLPrintData(nsAString &aXMLSubstr)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t generatedNameFormat;
+ rv = prefBranch->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &generatedNameFormat);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = stringBundleService->CreateBundle(sAddrbookProperties, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString generatedName;
+ rv = GenerateName(generatedNameFormat, bundle, generatedName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<mozITXTToHTMLConv> conv = do_CreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString xmlStr;
+ xmlStr.SetLength(4096); // to reduce allocations. should be enough for most cards
+ xmlStr.AssignLiteral("<GeneratedName>\n");
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText;
+ if (!generatedName.IsEmpty()) {
+ rv = conv->ScanTXT(generatedName.get(), mozITXTToHTMLConv::kEntities,
+ getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (safeText.IsEmpty()) {
+ nsAutoString primaryEmail;
+ GetPrimaryEmail(primaryEmail);
+
+ // use ScanTXT to convert < > & to safe values.
+ rv = conv->ScanTXT(primaryEmail.get(), mozITXTToHTMLConv::kEntities,
+ getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ xmlStr.Append(safeText);
+
+ xmlStr.AppendLiteral("</GeneratedName>\n"
+ "<table><tr><td>");
+
+ rv = AppendSection(NAME_ATTRS_ARRAY, sizeof(NAME_ATTRS_ARRAY)/sizeof(AppendItem), EmptyString(), bundle, conv, xmlStr);
+
+ xmlStr.AppendLiteral("</td></tr><tr><td>");
+
+ rv = AppendSection(PHONE_ATTRS_ARRAY, sizeof(PHONE_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingPhone"), bundle, conv, xmlStr);
+
+ if (!m_IsMailList) {
+ rv = AppendSection(CUSTOM_ATTRS_ARRAY, sizeof(CUSTOM_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingOther"), bundle, conv, xmlStr);
+#ifdef MOZ_THUNDERBIRD
+ rv = AppendSection(CHAT_ATTRS_ARRAY, sizeof(CHAT_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingChat"), bundle, conv, xmlStr);
+#endif
+ }
+ else {
+ rv = AppendSection(CUSTOM_ATTRS_ARRAY, sizeof(CUSTOM_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingDescription"),
+ bundle, conv, xmlStr);
+
+ xmlStr.AppendLiteral("<section><sectiontitle>");
+
+ nsString headingAddresses;
+ rv = bundle->GetStringFromName(u"headingAddresses", getter_Copies(headingAddresses));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ xmlStr.Append(headingAddresses);
+ xmlStr.AppendLiteral("</sectiontitle>");
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbDirectory> mailList = nullptr;
+ rv = abManager->GetDirectory(m_MailListURI, getter_AddRefs(mailList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> addresses;
+ rv = mailList->GetAddressLists(getter_AddRefs(addresses));
+ if (addresses) {
+ uint32_t total = 0;
+ addresses->GetLength(&total);
+ if (total) {
+ uint32_t i;
+ nsAutoString displayName;
+ nsAutoString primaryEmail;
+ for (i = 0; i < total; i++) {
+ nsCOMPtr <nsIAbCard> listCard = do_QueryElementAt(addresses, i, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ xmlStr.AppendLiteral("<PrimaryEmail>\n");
+
+ rv = listCard->GetDisplayName(displayName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText;
+ rv = conv->ScanTXT(displayName.get(), mozITXTToHTMLConv::kEntities,
+ getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ xmlStr.Append(safeText);
+
+ xmlStr.AppendLiteral(" &lt;");
+
+ listCard->GetPrimaryEmail(primaryEmail);
+
+ // use ScanTXT to convert < > & to safe values.
+ rv = conv->ScanTXT(primaryEmail.get(), mozITXTToHTMLConv::kEntities,
+ getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ xmlStr.Append(safeText);
+
+ xmlStr.AppendLiteral("&gt;</PrimaryEmail>\n");
+ }
+ }
+ }
+ xmlStr.AppendLiteral("</section>");
+ }
+
+ xmlStr.AppendLiteral("</td><td>");
+
+ rv = AppendSection(HOME_ATTRS_ARRAY, sizeof(HOME_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingHome"), bundle, conv, xmlStr);
+ rv = AppendSection(WORK_ATTRS_ARRAY, sizeof(WORK_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingWork"), bundle, conv, xmlStr);
+
+ xmlStr.AppendLiteral("</td></tr></table>");
+
+ aXMLSubstr = xmlStr;
+
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::AppendSection(const AppendItem *aArray, int16_t aCount, const nsString& aHeading,
+ nsIStringBundle *aBundle,
+ mozITXTToHTMLConv *aConv,
+ nsString &aResult)
+{
+ nsresult rv = NS_OK;
+
+ aResult.AppendLiteral("<section>");
+
+ nsString attrValue;
+ bool sectionIsEmpty = true;
+
+ int16_t i = 0;
+ for (i=0;i<aCount;i++) {
+ rv = GetPropertyAsAString(aArray[i].mColumn, attrValue);
+ if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty())
+ sectionIsEmpty = false;
+ }
+
+ if (!sectionIsEmpty && !aHeading.IsEmpty()) {
+ nsString heading;
+ rv = aBundle->GetStringFromName(aHeading.get(), getter_Copies(heading));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.AppendLiteral("<sectiontitle>");
+ aResult.Append(heading);
+ aResult.AppendLiteral("</sectiontitle>");
+ }
+
+ for (i=0;i<aCount;i++) {
+ switch (aArray[i].mAppendType) {
+ case eAppendLine:
+ rv = AppendLine(aArray[i], aConv, aResult);
+ break;
+ case eAppendLabel:
+ rv = AppendLabel(aArray[i], aBundle, aConv, aResult);
+ break;
+ case eAppendCityStateZip:
+ rv = AppendCityStateZip(aArray[i], aBundle, aConv, aResult);
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("append item failed");
+ break;
+ }
+ }
+ aResult.AppendLiteral("</section>");
+
+ return rv;
+}
+
+nsresult nsAbCardProperty::AppendLine(const AppendItem &aItem,
+ mozITXTToHTMLConv *aConv,
+ nsString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(aConv);
+
+ nsString attrValue;
+ nsresult rv = GetPropertyAsAString(aItem.mColumn, attrValue);
+
+ if (NS_FAILED(rv) || attrValue.IsEmpty())
+ return NS_OK;
+
+ aResult.Append(char16_t('<'));
+ aResult.Append(NS_ConvertUTF8toUTF16(aItem.mColumn));
+ aResult.Append(char16_t('>'));
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText;
+ rv = aConv->ScanTXT(attrValue.get(), mozITXTToHTMLConv::kEntities, getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult.Append(safeText);
+
+ aResult.AppendLiteral("</");
+ aResult.Append(NS_ConvertUTF8toUTF16(aItem.mColumn));
+ aResult.Append(char16_t('>'));
+
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::AppendLabel(const AppendItem &aItem,
+ nsIStringBundle *aBundle,
+ mozITXTToHTMLConv *aConv,
+ nsString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+
+ nsresult rv;
+ nsString label, attrValue;
+
+ rv = GetPropertyAsAString(aItem.mColumn, attrValue);
+
+ if (NS_FAILED(rv) || attrValue.IsEmpty())
+ return NS_OK;
+
+ rv = aBundle->GetStringFromName(NS_ConvertUTF8toUTF16(aItem.mLabel).get(), getter_Copies(label));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.AppendLiteral("<labelrow><label>");
+
+ aResult.Append(label);
+ aResult.AppendLiteral(": </label>");
+
+ rv = AppendLine(aItem, aConv, aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ aResult.AppendLiteral("</labelrow>");
+
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::AppendCityStateZip(const AppendItem &aItem,
+ nsIStringBundle *aBundle,
+ mozITXTToHTMLConv *aConv,
+ nsString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+
+ nsresult rv;
+ AppendItem item;
+ const char *statePropName, *zipPropName;
+
+ if (strcmp(aItem.mColumn, kHomeCityProperty) == 0) {
+ statePropName = kHomeStateProperty;
+ zipPropName = kHomeZipCodeProperty;
+ }
+ else {
+ statePropName = kWorkStateProperty;
+ zipPropName = kWorkZipCodeProperty;
+ }
+
+ nsAutoString cityResult, stateResult, zipResult;
+
+ rv = AppendLine(aItem, aConv, cityResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ item.mColumn = statePropName;
+ item.mLabel = "";
+
+ rv = AppendLine(item, aConv, stateResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ item.mColumn = zipPropName;
+
+ rv = AppendLine(item, aConv, zipResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString formattedString;
+
+ if (!cityResult.IsEmpty() && !stateResult.IsEmpty() && !zipResult.IsEmpty()) {
+ const char16_t *formatStrings[] = { cityResult.get(), stateResult.get(), zipResult.get() };
+ rv = aBundle->FormatStringFromName(u"cityAndStateAndZip", formatStrings, ArrayLength(formatStrings), getter_Copies(formattedString));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else if (!cityResult.IsEmpty() && !stateResult.IsEmpty() && zipResult.IsEmpty()) {
+ const char16_t *formatStrings[] = { cityResult.get(), stateResult.get() };
+ rv = aBundle->FormatStringFromName(u"cityAndStateNoZip", formatStrings, ArrayLength(formatStrings), getter_Copies(formattedString));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else if ((!cityResult.IsEmpty() && stateResult.IsEmpty() && !zipResult.IsEmpty()) ||
+ (cityResult.IsEmpty() && !stateResult.IsEmpty() && !zipResult.IsEmpty())) {
+ const char16_t *formatStrings[] = { cityResult.IsEmpty() ? stateResult.get() : cityResult.get(), zipResult.get() };
+ rv = aBundle->FormatStringFromName(u"cityOrStateAndZip", formatStrings, ArrayLength(formatStrings), getter_Copies(formattedString));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else {
+ if (!cityResult.IsEmpty())
+ formattedString = cityResult;
+ else if (!stateResult.IsEmpty())
+ formattedString = stateResult;
+ else
+ formattedString = zipResult;
+ }
+
+ aResult.Append(formattedString);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GenerateName(int32_t aGenerateFormat,
+ nsIStringBundle* aBundle,
+ nsAString &aResult)
+{
+ aResult.Truncate();
+
+ // Cache the first and last names
+ nsAutoString firstName, lastName;
+ GetFirstName(firstName);
+ GetLastName(lastName);
+
+ // No need to check for aBundle present straight away, only do that if we're
+ // actually going to use it.
+ if (aGenerateFormat == GENERATE_DISPLAY_NAME)
+ GetDisplayName(aResult);
+ else if (lastName.IsEmpty())
+ aResult = firstName;
+ else if (firstName.IsEmpty())
+ aResult = lastName;
+ else {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundle> bundle(aBundle);
+ if (!bundle) {
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED);
+
+ rv = stringBundleService->CreateBundle(sAddrbookProperties,
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsString result;
+
+ if (aGenerateFormat == GENERATE_LAST_FIRST_ORDER) {
+ const char16_t *stringParams[2] = {lastName.get(), firstName.get()};
+
+ rv = bundle->FormatStringFromName(u"lastFirstFormat",
+ stringParams, 2, getter_Copies(result));
+ }
+ else {
+ const char16_t *stringParams[2] = {firstName.get(), lastName.get()};
+
+ rv = bundle->FormatStringFromName(u"firstLastFormat",
+ stringParams, 2, getter_Copies(result));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.Assign(result);
+ }
+
+ if (aResult.IsEmpty())
+ {
+ // The normal names have failed, does this card have a company name? If so,
+ // use that instead, because that is likely to be more meaningful than an
+ // email address.
+ //
+ // If this errors, the string isn't found and we'll fall into the next
+ // check.
+ (void) GetPropertyAsAString(kCompanyProperty, aResult);
+ }
+
+ if (aResult.IsEmpty())
+ {
+ // see bug #211078
+ // if there is no generated name at this point
+ // use the userid from the email address
+ // it is better than nothing.
+ GetPrimaryEmail(aResult);
+ int32_t index = aResult.FindChar('@');
+ if (index != -1)
+ aResult.SetLength(index);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GeneratePhoneticName(bool aLastNameFirst,
+ nsAString &aResult)
+{
+ nsAutoString firstName, lastName;
+ GetPropertyAsAString(kPhoneticFirstNameProperty, firstName);
+ GetPropertyAsAString(kPhoneticLastNameProperty, lastName);
+
+ if (aLastNameFirst)
+ {
+ aResult = lastName;
+ aResult += firstName;
+ }
+ else
+ {
+ aResult = firstName;
+ aResult += lastName;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GenerateChatName(nsAString &aResult)
+{
+ aResult.Truncate();
+
+#define CHECK_CHAT_PROPERTY(aProtocol) \
+ if (NS_SUCCEEDED(GetPropertyAsAString(k##aProtocol##Property, aResult)) && \
+ !aResult.IsEmpty()) \
+ return NS_OK
+ CHECK_CHAT_PROPERTY(Gtalk);
+ CHECK_CHAT_PROPERTY(AIM);
+ CHECK_CHAT_PROPERTY(Yahoo);
+ CHECK_CHAT_PROPERTY(Skype);
+ CHECK_CHAT_PROPERTY(QQ);
+ CHECK_CHAT_PROPERTY(MSN);
+ CHECK_CHAT_PROPERTY(ICQ);
+ CHECK_CHAT_PROPERTY(XMPP);
+ CHECK_CHAT_PROPERTY(IRC);
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbCardProperty.h b/mailnews/addrbook/src/nsAbCardProperty.h
new file mode 100644
index 000000000..46ba50079
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbCardProperty.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Person Card Property
+
+*********************************************************************************************************/
+
+#ifndef nsAbCardProperty_h__
+#define nsAbCardProperty_h__
+
+#include "nsIAbCard.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+#include "nsInterfaceHashtable.h"
+#include "nsIVariant.h"
+
+class nsIStringBundle;
+class mozITXTToHTMLConv;
+struct AppendItem;
+
+ /*
+ * Address Book Card Property
+ */
+
+class nsAbCardProperty: public nsIAbCard
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABCARD
+ NS_DECL_NSIABITEM
+
+ nsAbCardProperty();
+
+protected:
+ virtual ~nsAbCardProperty();
+ bool m_IsMailList;
+ nsCString m_MailListURI;
+
+ // Store most of the properties here
+ nsInterfaceHashtable<nsCStringHashKey, nsIVariant> m_properties;
+
+ nsCString m_directoryId, m_localId;
+private:
+ nsresult AppendSection(const AppendItem *aArray, int16_t aCount, const nsString& aHeading, nsIStringBundle *aBundle, mozITXTToHTMLConv *aConv, nsString &aResult);
+ nsresult AppendLine(const AppendItem &aItem, mozITXTToHTMLConv *aConv, nsString &aResult);
+ nsresult AppendLabel(const AppendItem &aItem, nsIStringBundle *aBundle, mozITXTToHTMLConv *aConv, nsString &aResult);
+ nsresult AppendCityStateZip(const AppendItem &aItem, nsIStringBundle *aBundle, mozITXTToHTMLConv *aConv, nsString &aResult);
+
+ nsresult ConvertToBase64EncodedXML(nsACString &result);
+ nsresult ConvertToXMLPrintData(nsAString &result);
+ nsresult ConvertToEscapedVCard(nsACString &result);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbContentHandler.cpp b/mailnews/addrbook/src/nsAbContentHandler.cpp
new file mode 100644
index 000000000..6e283d82b
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbContentHandler.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbContentHandler.h"
+#include "nsAbBaseCID.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsNullPrincipal.h"
+#include "nsISupportsPrimitives.h"
+#include "plstr.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgVCardService.h"
+#include "nsIAbCard.h"
+#include "nsIAbManager.h"
+#include "nsVCard.h"
+#include "nsIChannel.h"
+//
+// nsAbContentHandler
+//
+nsAbContentHandler::nsAbContentHandler()
+{
+}
+
+nsAbContentHandler::~nsAbContentHandler()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAbContentHandler, nsIContentHandler,
+ nsIStreamLoaderObserver)
+
+NS_IMETHODIMP
+nsAbContentHandler::HandleContent(const char *aContentType,
+ nsIInterfaceRequestor *aWindowContext,
+ nsIRequest *request)
+{
+ NS_ENSURE_ARG_POINTER(request);
+
+ nsresult rv = NS_OK;
+
+ // First of all, get the content type and make sure it is a content type we know how to handle!
+ if (PL_strcasecmp(aContentType, "application/x-addvcard") == 0) {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel) return NS_ERROR_FAILURE;
+
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ {
+ nsAutoCString path;
+ rv = uri->GetPath(path);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ const char *startOfVCard = strstr(path.get(), "add?vcard=");
+ if (startOfVCard)
+ {
+ nsCString unescapedData;
+
+ // XXX todo, explain why we is escaped twice
+ MsgUnescapeString(nsDependentCString(startOfVCard + strlen("add?vcard=")),
+ 0, unescapedData);
+
+ if (!aWindowContext)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aWindowContext);
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow = nsPIDOMWindowOuter::From(domWindow);
+ parentWindow = parentWindow->GetOuterWindow();
+ NS_ENSURE_ARG_POINTER(parentWindow);
+
+ nsCOMPtr<nsIAbManager> ab =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbCard> cardFromVCard;
+ rv = ab->EscapedVCardToAbCard(unescapedData.get(),
+ getter_AddRefs(cardFromVCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ifptr->SetData(cardFromVCard);
+ ifptr->SetDataIID(&NS_GET_IID(nsIAbCard));
+
+ nsCOMPtr<nsPIDOMWindowOuter> dialogWindow;
+
+ rv = parentWindow->OpenDialog(
+ NS_LITERAL_STRING("chrome://messenger/content/addressbook/abNewCardDialog.xul"),
+ EmptyString(),
+ NS_LITERAL_STRING("chrome,resizable=no,titlebar,modal,centerscreen"),
+ ifptr, getter_AddRefs(dialogWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = NS_OK;
+ }
+ }
+ else if (PL_strcasecmp(aContentType, "text/x-vcard") == 0) {
+ // create a vcard stream listener that can parse the data stream
+ // and bring up the appropriate UI
+
+ // (1) cancel the current load operation. We'll restart it
+ request->Cancel(NS_ERROR_ABORT);
+ // get the url we were trying to open
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
+
+ rv = channel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create a stream loader to handle the v-card data
+ nsCOMPtr<nsIStreamLoader> streamLoader;
+ rv = NS_NewStreamLoader(getter_AddRefs(streamLoader),
+ uri,
+ this,
+ nullPrincipal,
+ nsILoadInfo::SEC_NORMAL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ }
+ else // The content-type was not application/x-addvcard...
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAbContentHandler::OnStreamComplete(nsIStreamLoader *aLoader,
+ nsISupports *aContext, nsresult aStatus,
+ uint32_t datalen, const uint8_t *data)
+{
+ NS_ENSURE_ARG_POINTER(aContext);
+ NS_ENSURE_SUCCESS(aStatus, aStatus); // don't process the vcard if we got a status error
+ nsresult rv = NS_OK;
+
+ // take our vCard string and open up an address book window based on it
+ nsCOMPtr<nsIMsgVCardService> vCardService = do_GetService(NS_MSGVCARDSERVICE_CONTRACTID);
+ if (vCardService)
+ {
+ nsAutoPtr<VObject> vObj(vCardService->Parse_MIME((const char *)data, datalen));
+ if (vObj)
+ {
+ int32_t len = 0;
+ nsCString vCard;
+ vCard.Adopt(vCardService->WriteMemoryVObjects(0, &len, vObj, false));
+
+ nsCOMPtr<nsIAbManager> ab =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbCard> cardFromVCard;
+ rv = ab->EscapedVCardToAbCard(vCard.get(),
+ getter_AddRefs(cardFromVCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aContext);
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow = nsPIDOMWindowOuter::From(domWindow);
+ parentWindow = parentWindow->GetOuterWindow();
+ NS_ENSURE_ARG_POINTER(parentWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> dialogWindow;
+ rv = parentWindow->OpenDialog(
+ NS_LITERAL_STRING("chrome://messenger/content/addressbook/abNewCardDialog.xul"),
+ EmptyString(),
+ NS_LITERAL_STRING("chrome,resizable=no,titlebar,modal,centerscreen"),
+ cardFromVCard, getter_AddRefs(dialogWindow));
+ }
+ }
+
+ return rv;
+}
diff --git a/mailnews/addrbook/src/nsAbContentHandler.h b/mailnews/addrbook/src/nsAbContentHandler.h
new file mode 100644
index 000000000..e5405ae0e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbContentHandler.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsAbContentHandler_h
+#define __nsAbContentHandler_h
+
+#include "nsIStreamLoader.h"
+#include "nsIContentHandler.h"
+
+class nsAbContentHandler : public nsIContentHandler,
+ public nsIStreamLoaderObserver
+{
+public:
+ nsAbContentHandler();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICONTENTHANDLER
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+private:
+ virtual ~nsAbContentHandler();
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbDirFactoryService.cpp b/mailnews/addrbook/src/nsAbDirFactoryService.cpp
new file mode 100644
index 000000000..4ddf8e635
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirFactoryService.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsMemory.h"
+#include "nsStringGlue.h"
+#include "plstr.h"
+
+#include "nsAbBaseCID.h"
+#include "nsAbDirFactoryService.h"
+#include "nsIAbDirFactory.h"
+#include "mozilla/Services.h"
+
+NS_IMPL_ISUPPORTS(nsAbDirFactoryService, nsIAbDirFactoryService)
+
+nsAbDirFactoryService::nsAbDirFactoryService()
+{
+}
+
+nsAbDirFactoryService::~nsAbDirFactoryService()
+{
+}
+
+/* nsIAbDirFactory getDirFactory (in string uri); */
+NS_IMETHODIMP
+nsAbDirFactoryService::GetDirFactory(const nsACString &aURI,
+ nsIAbDirFactory** aDirFactory)
+{
+ NS_ENSURE_ARG_POINTER(aDirFactory);
+
+ nsresult rv;
+
+ // Obtain the network IO service
+ nsCOMPtr<nsIIOService> nsService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(nsService, NS_ERROR_UNEXPECTED);
+
+ // Extract the scheme
+ nsAutoCString scheme;
+ rv = nsService->ExtractScheme(aURI, scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Try to find a factory using the component manager.
+ nsAutoCString contractID;
+ contractID.AssignLiteral(NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX);
+ contractID.Append(scheme);
+
+ return CallCreateInstance(contractID.get(), aDirFactory);
+}
diff --git a/mailnews/addrbook/src/nsAbDirFactoryService.h b/mailnews/addrbook/src/nsAbDirFactoryService.h
new file mode 100644
index 000000000..77396fbaa
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirFactoryService.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbDirFactoryService_h__
+#define nsAbDirFactoryService_h__
+
+#include "nsIAbDirFactoryService.h"
+
+class nsAbDirFactoryService : public nsIAbDirFactoryService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORYSERVICE
+
+ nsAbDirFactoryService();
+
+private:
+ virtual ~nsAbDirFactoryService();
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbDirProperty.cpp b/mailnews/addrbook/src/nsAbDirProperty.cpp
new file mode 100644
index 000000000..fbae06220
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirProperty.cpp
@@ -0,0 +1,593 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbDirProperty.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbCard.h"
+#include "nsDirPrefs.h"
+#include "nsIPrefService.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "prmem.h"
+#include "nsIAbManager.h"
+#include "nsArrayUtils.h"
+
+// From nsDirPrefs
+#define kDefaultPosition 1
+
+nsAbDirProperty::nsAbDirProperty(void)
+ : m_LastModifiedDate(0),
+ mIsValidURI(false),
+ mIsQueryURI(false)
+{
+ m_IsMailList = false;
+}
+
+nsAbDirProperty::~nsAbDirProperty(void)
+{
+#if 0
+ // this code causes a regression #138647
+ // don't turn it on until you figure it out
+ if (m_AddressList) {
+ uint32_t count;
+ nsresult rv;
+ rv = m_AddressList->GetLength(&count);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Count failed");
+ int32_t i;
+ for (i = count - 1; i >= 0; i--)
+ m_AddressList->RemoveElementAt(i);
+ }
+#endif
+}
+
+NS_IMPL_ISUPPORTS(nsAbDirProperty, nsIAbDirectory, nsISupportsWeakReference,
+ nsIAbCollection, nsIAbItem)
+
+NS_IMETHODIMP nsAbDirProperty::GetUuid(nsACString &uuid)
+{
+ // XXX: not all directories have a dirPrefId...
+ nsresult rv = GetDirPrefId(uuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uuid.Append('&');
+ nsString dirName;
+ GetDirName(dirName);
+ uuid.Append(NS_ConvertUTF16toUTF8(dirName));
+ return rv;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GenerateName(int32_t aGenerateFormat,
+ nsIStringBundle *aBundle,
+ nsAString &name)
+{
+ return GetDirName(name);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetPropertiesChromeURI(nsACString &aResult)
+{
+ aResult.AssignLiteral("chrome://messenger/content/addressbook/abAddressBookNameDialog.xul");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDirName(nsAString &aDirName)
+{
+ if (m_DirPrefId.IsEmpty())
+ {
+ aDirName = m_ListDirName;
+ return NS_OK;
+ }
+
+ nsCString dirName;
+ nsresult rv = GetLocalizedStringValue("description", EmptyCString(), dirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // In TB 2 only some prefs had chrome:// URIs. We had code in place that would
+ // only get the localized string pref for the particular address books that
+ // were built-in.
+ // Additionally, nsIPrefBranch::getComplexValue will only get a non-user-set,
+ // non-locked pref value if it is a chrome:// URI and will get the string
+ // value at that chrome URI. This breaks extensions/autoconfig that want to
+ // set default pref values and allow users to change directory names.
+ //
+ // Now we have to support this, and so if for whatever reason we fail to get
+ // the localized version, then we try and get the non-localized version
+ // instead. If the string value is empty, then we'll just get the empty value
+ // back here.
+ if (dirName.IsEmpty())
+ {
+ rv = GetStringValue("description", EmptyCString(), dirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ CopyUTF8toUTF16(dirName, aDirName);
+ return NS_OK;
+}
+
+// XXX Although mailing lists could use the NotifyItemPropertyChanged
+// mechanism here, it requires some rework on how we write/save data
+// relating to mailing lists, so we're just using the old method of a
+// local variable to store the mailing list name.
+NS_IMETHODIMP nsAbDirProperty::SetDirName(const nsAString &aDirName)
+{
+ if (m_DirPrefId.IsEmpty())
+ {
+ m_ListDirName = aDirName;
+ return NS_OK;
+ }
+
+ // Store the old value.
+ nsString oldDirName;
+ nsresult rv = GetDirName(oldDirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Save the new value
+ rv = SetLocalizedStringValue("description", NS_ConvertUTF16toUTF8(aDirName));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ // We inherit from nsIAbDirectory, so this static cast should be safe.
+ abManager->NotifyItemPropertyChanged(static_cast<nsIAbDirectory*>(this),
+ "DirName", oldDirName.get(),
+ nsString(aDirName).get());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDirType(int32_t *aDirType)
+{
+ return GetIntValue("dirType", LDAPDirectory, aDirType);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetFileName(nsACString &aFileName)
+{
+ return GetStringValue("filename", EmptyCString(), aFileName);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetURI(nsACString &aURI)
+{
+ // XXX Should we complete this for Mailing Lists?
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetPosition(int32_t *aPosition)
+{
+ return GetIntValue("position", kDefaultPosition, aPosition);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetLastModifiedDate(uint32_t *aLastModifiedDate)
+{
+ NS_ENSURE_ARG_POINTER(aLastModifiedDate);
+ *aLastModifiedDate = m_LastModifiedDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetLastModifiedDate(uint32_t aLastModifiedDate)
+{
+ if (aLastModifiedDate)
+ {
+ m_LastModifiedDate = aLastModifiedDate;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetListNickName(nsAString &aListNickName)
+{
+ aListNickName = m_ListNickName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetListNickName(const nsAString &aListNickName)
+{
+ m_ListNickName = aListNickName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDescription(nsAString &aDescription)
+{
+ aDescription = m_Description;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetDescription(const nsAString &aDescription)
+{
+ m_Description = aDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsMailList(bool *aIsMailList)
+{
+ *aIsMailList = m_IsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetIsMailList(bool aIsMailList)
+{
+ m_IsMailList = aIsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetAddressLists(nsIMutableArray * *aAddressLists)
+{
+ if (!m_AddressList)
+ {
+ nsresult rv;
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aAddressLists = m_AddressList;
+ NS_ADDREF(*aAddressLists);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetAddressLists(nsIMutableArray * aAddressLists)
+{
+ m_AddressList = aAddressLists;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::CopyMailList(nsIAbDirectory* srcList)
+{
+ SetIsMailList(true);
+
+ nsString str;
+ srcList->GetDirName(str);
+ SetDirName(str);
+ srcList->GetListNickName(str);
+ SetListNickName(str);
+ srcList->GetDescription(str);
+ SetDescription(str);
+
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ srcList->GetAddressLists(getter_AddRefs(pAddressLists));
+ SetAddressLists(pAddressLists);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // Mailing lists are not queries by default, individual directory types
+ // will override this.
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::Init(const char *aURI)
+{
+ mURINoQuery = aURI;
+ mURI = aURI;
+ mIsValidURI = true;
+
+ int32_t searchCharLocation = mURINoQuery.FindChar('?');
+ if (searchCharLocation >= 0)
+ {
+ mQueryString = Substring(mURINoQuery, searchCharLocation + 1);
+ mURINoQuery.SetLength(searchCharLocation);
+ mIsQueryURI = true;
+ }
+
+ return NS_OK;
+}
+
+// nsIAbDirectory NOT IMPLEMENTED methods
+NS_IMETHODIMP
+nsAbDirProperty::GetChildNodes(nsISimpleEnumerator **childList)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::GetChildCards(nsISimpleEnumerator **childCards)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::DeleteDirectory(nsIAbDirectory *directory)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::HasCard(nsIAbCard *cards, bool *hasCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::HasDirectory(nsIAbDirectory *dir, bool *hasDir)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::HasMailListWithName(const char16_t *aName, bool *aHasList)
+{
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ENSURE_ARG_POINTER(aHasList);
+
+ *aHasList = false;
+
+ bool supportsLists = false;
+ nsresult rv = GetSupportsMailingLists(&supportsLists);
+ if (NS_FAILED(rv) || !supportsLists)
+ return NS_OK;
+
+ if (m_IsMailList)
+ return NS_OK;
+
+ nsCOMPtr<nsIMutableArray> addressLists;
+ rv = GetAddressLists(getter_AddRefs(addressLists));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t listCount = 0;
+ rv = addressLists->GetLength(&listCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < listCount; i++)
+ {
+ nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(addressLists, i, &rv));
+ if (NS_SUCCEEDED(rv) && listDir)
+ {
+ nsAutoString listName;
+ rv = listDir->GetDirName(listName);
+ if (NS_SUCCEEDED(rv) && listName.Equals(aName))
+ {
+ *aHasList = true;
+ return NS_OK;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::CreateNewDirectory(const nsAString &aDirName,
+ const nsACString &aURI,
+ uint32_t aType,
+ const nsACString &aPrefName,
+ nsACString &aResult)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::CreateDirectoryByURI(const nsAString &aDisplayName,
+ const nsACString &aURI)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::AddMailList(nsIAbDirectory *list, nsIAbDirectory **addedList)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::EditMailListToDatabase(nsIAbCard *listCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::AddCard(nsIAbCard *childCard, nsIAbCard **addedCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::ModifyCard(nsIAbCard *aModifiedCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::DeleteCards(nsIArray *cards)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::DropCard(nsIAbCard *childCard, bool needToCopyCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::CardForEmailAddress(const nsACString &aEmailAddress,
+ nsIAbCard ** aAbCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsIAbCard **result)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsISimpleEnumerator **result)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+
+NS_IMETHODIMP nsAbDirProperty::GetSupportsMailingLists(bool *aSupportsMailingsLists)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsMailingsLists);
+ // We don't currently support nested mailing lists, so only return true if
+ // we're not a mailing list.
+ *aSupportsMailingsLists = !m_IsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetReadOnly(bool *aReadOnly)
+{
+ NS_ENSURE_ARG_POINTER(aReadOnly);
+ // Default is that we are writable. Any implementation that is read-only must
+ // override this method.
+ *aReadOnly = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsRemote(bool *aIsRemote)
+{
+ NS_ENSURE_ARG_POINTER(aIsRemote);
+ *aIsRemote = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsSecure(bool *aIsSecure)
+{
+ NS_ENSURE_ARG_POINTER(aIsSecure);
+ *aIsSecure = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::UseForAutocomplete(const nsACString &aIdentityKey,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // Is local autocomplete enabled?
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return prefBranch->GetBoolPref("mail.enable_autocomplete", aResult);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDirPrefId(nsACString &aDirPrefId)
+{
+ aDirPrefId = m_DirPrefId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetDirPrefId(const nsACString &aDirPrefId)
+{
+ if (!m_DirPrefId.Equals(aDirPrefId))
+ {
+ m_DirPrefId.Assign(aDirPrefId);
+ // Clear the directory pref branch so that it is re-initialized next
+ // time its required.
+ m_DirectoryPrefs = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult nsAbDirProperty::InitDirectoryPrefs()
+{
+ if (m_DirPrefId.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString realPrefId(m_DirPrefId);
+ realPrefId.Append('.');
+
+ return prefService->GetBranch(realPrefId.get(), getter_AddRefs(m_DirectoryPrefs));
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIntValue(const char *aName,
+ int32_t aDefaultValue,
+ int32_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (NS_FAILED(m_DirectoryPrefs->GetIntPref(aName, aResult)))
+ *aResult = aDefaultValue;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetBoolValue(const char *aName,
+ bool aDefaultValue,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (NS_FAILED(m_DirectoryPrefs->GetBoolPref(aName, aResult)))
+ *aResult = aDefaultValue;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetStringValue(const char *aName,
+ const nsACString &aDefaultValue,
+ nsACString &aResult)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString value;
+
+ /* unfortunately, there may be some prefs out there which look like (null) */
+ if (NS_SUCCEEDED(m_DirectoryPrefs->GetCharPref(aName, getter_Copies(value))) &&
+ !value.EqualsLiteral("(null"))
+ aResult = value;
+ else
+ aResult = aDefaultValue;
+
+ return NS_OK;
+}
+/*
+ * Get localized unicode string pref from properties file, convert into an
+ * UTF8 string since address book prefs store as UTF8 strings. So far there
+ * are 2 default prefs stored in addressbook.properties.
+ * "ldap_2.servers.pab.description"
+ * "ldap_2.servers.history.description"
+ */
+NS_IMETHODIMP nsAbDirProperty::GetLocalizedStringValue(const char *aName,
+ const nsACString &aDefaultValue,
+ nsACString &aResult)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsString wvalue;
+ nsCOMPtr<nsIPrefLocalizedString> locStr;
+
+ nsresult rv = m_DirectoryPrefs->GetComplexValue(aName,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(locStr));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = locStr->ToString(getter_Copies(wvalue));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (wvalue.IsEmpty())
+ aResult = aDefaultValue;
+ else
+ CopyUTF16toUTF8(wvalue, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetIntValue(const char *aName,
+ int32_t aValue)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return m_DirectoryPrefs->SetIntPref(aName, aValue);
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetBoolValue(const char *aName,
+ bool aValue)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return m_DirectoryPrefs->SetBoolPref(aName, aValue);
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetStringValue(const char *aName,
+ const nsACString &aValue)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return m_DirectoryPrefs->SetCharPref(aName, nsCString(aValue).get());
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetLocalizedStringValue(const char *aName,
+ const nsACString &aValue)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefLocalizedString> locStr(
+ do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = locStr->SetData(NS_ConvertUTF8toUTF16(aValue).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return m_DirectoryPrefs->SetComplexValue(aName,
+ NS_GET_IID(nsIPrefLocalizedString),
+ locStr);
+}
diff --git a/mailnews/addrbook/src/nsAbDirProperty.h b/mailnews/addrbook/src/nsAbDirProperty.h
new file mode 100644
index 000000000..99d16a133
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirProperty.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Directory
+
+*********************************************************************************************************/
+
+#ifndef nsAbDirProperty_h__
+#define nsAbDirProperty_h__
+
+#include "nsIAbDirectory.h" /* include the interface we are going to support */
+#include "nsIAbCard.h"
+#include "nsCOMPtr.h"
+#include "nsDirPrefs.h"
+#include "nsIAddrDatabase.h"
+#include "nsStringGlue.h"
+#include "nsIPrefBranch.h"
+#include "nsIMutableArray.h"
+#include "nsWeakReference.h"
+
+ /*
+ * Address Book Directory
+ */
+
+class nsAbDirProperty: public nsIAbDirectory,
+ public nsSupportsWeakReference
+{
+public:
+ nsAbDirProperty(void);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABITEM
+ NS_DECL_NSIABCOLLECTION
+ NS_DECL_NSIABDIRECTORY
+
+protected:
+ virtual ~nsAbDirProperty(void);
+
+ /**
+ * Initialise the directory prefs for this branch
+ */
+ nsresult InitDirectoryPrefs();
+
+ uint32_t m_LastModifiedDate;
+
+ nsString m_ListDirName;
+ nsString m_ListName;
+ nsString m_ListNickName;
+ nsString m_Description;
+ bool m_IsMailList;
+
+ nsCString mURI;
+ nsCString mQueryString;
+ nsCString mURINoQuery;
+ bool mIsValidURI;
+ bool mIsQueryURI;
+
+
+ /*
+ * Note that any derived implementations should ensure that this item
+ * (m_DirPrefId) is correctly initialised correctly
+ */
+ nsCString m_DirPrefId; // ie,"ldap_2.servers.pab"
+
+ nsCOMPtr<nsIPrefBranch> m_DirectoryPrefs;
+ nsCOMPtr<nsIMutableArray> m_AddressList;
+};
+#endif
diff --git a/mailnews/addrbook/src/nsAbDirectoryQuery.cpp b/mailnews/addrbook/src/nsAbDirectoryQuery.cpp
new file mode 100644
index 000000000..9c9826ac8
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirectoryQuery.cpp
@@ -0,0 +1,528 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbDirectoryQuery.h"
+#include "nsAbDirectoryQueryProxy.h"
+#include "nsAbUtils.h"
+#include "nsAbBooleanExpression.h"
+#include "nsArrayUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStringGlue.h"
+#include "nsUnicharUtils.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsISimpleEnumerator.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQuerySimpleBooleanExpression, nsIAbBooleanExpression)
+
+nsAbDirectoryQuerySimpleBooleanExpression::nsAbDirectoryQuerySimpleBooleanExpression() :
+ mOperation (nsIAbBooleanOperationTypes::AND)
+{
+}
+
+nsAbDirectoryQuerySimpleBooleanExpression::~nsAbDirectoryQuerySimpleBooleanExpression()
+{
+}
+
+/* attribute nsAbBooleanOperationType operation; */
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::GetOperation(nsAbBooleanOperationType *aOperation)
+{
+ if (!aOperation)
+ return NS_ERROR_NULL_POINTER;
+
+ *aOperation = mOperation;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::SetOperation(nsAbBooleanOperationType aOperation)
+{
+ if (aOperation != nsIAbBooleanOperationTypes::AND &&
+ aOperation != nsIAbBooleanOperationTypes::OR)
+ return NS_ERROR_FAILURE;
+
+ mOperation = aOperation;
+
+ return NS_OK;
+}
+
+/* attribute nsIArray expressions; */
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::GetExpressions(nsIArray **aExpressions)
+{
+ if (!aExpressions)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!mExpressions)
+ {
+ mExpressions = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ if (!mExpressions)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ADDREF(*aExpressions = mExpressions);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::SetExpressions(nsIArray *aExpressions)
+{
+ if (!aExpressions)
+ return NS_ERROR_NULL_POINTER;
+
+ // Ensure all the items are of the right type.
+ nsresult rv;
+ uint32_t count;
+ rv = aExpressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanConditionString> queryExpression;
+
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ queryExpression = do_QueryElementAt(aExpressions, i, &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Values ok, so we can just save and return.
+ mExpressions = aExpressions;
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQueryArguments, nsIAbDirectoryQueryArguments)
+
+nsAbDirectoryQueryArguments::nsAbDirectoryQueryArguments() :
+ mQuerySubDirectories(true)
+{
+}
+
+nsAbDirectoryQueryArguments::~nsAbDirectoryQueryArguments()
+{
+}
+
+/* attribute nsISupports matchItems; */
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetExpression(nsISupports** aExpression)
+{
+ if (!aExpression)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_IF_ADDREF(*aExpression = mExpression);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetExpression(nsISupports* aExpression)
+{
+ mExpression = aExpression;
+ return NS_OK;
+}
+
+/* attribute boolean querySubDirectories; */
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetQuerySubDirectories(bool* aQuerySubDirectories)
+{
+ NS_ENSURE_ARG_POINTER(aQuerySubDirectories);
+ *aQuerySubDirectories = mQuerySubDirectories;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetQuerySubDirectories(bool aQuerySubDirectories)
+{
+ mQuerySubDirectories = aQuerySubDirectories;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetTypeSpecificArg(nsISupports** aArg)
+{
+ NS_ENSURE_ARG_POINTER(aArg);
+
+ NS_IF_ADDREF(*aArg = mTypeSpecificArg);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetTypeSpecificArg(nsISupports* aArg)
+{
+ mTypeSpecificArg = aArg;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetFilter(nsACString & aFilter)
+{
+ aFilter.Assign(mFilter);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetFilter(const nsACString & aFilter)
+{
+ mFilter.Assign(aFilter);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQueryPropertyValue, nsIAbDirectoryQueryPropertyValue)
+
+nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue()
+{
+}
+
+nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue(const char* aName,
+ const char16_t* aValue)
+{
+ mName = aName;
+ mValue = aValue;
+}
+
+nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue(const char* aName,
+ nsISupports* aValueISupports)
+{
+ mName = aName;
+ mValueISupports = aValueISupports;
+}
+
+nsAbDirectoryQueryPropertyValue::~nsAbDirectoryQueryPropertyValue()
+{
+}
+
+/* read only attribute string name; */
+NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetName(char* *aName)
+{
+ *aName = mName.IsEmpty() ? 0 : ToNewCString(mName);
+
+ return NS_OK;
+}
+
+/* read only attribute wstring value; */
+NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetValue(char16_t* *aValue)
+{
+ *aValue = ToNewUnicode(mValue);
+ if (!(*aValue))
+ return NS_ERROR_OUT_OF_MEMORY;
+ else
+ return NS_OK;
+}
+
+/* readonly attribute nsISupports valueISupports; */
+NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetValueISupports(nsISupports* *aValueISupports)
+{
+ if (!mValueISupports)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_IF_ADDREF(*aValueISupports = mValueISupports);
+ return NS_OK;
+}
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsAbDirectoryQuery, nsIAbDirectoryQuery)
+
+nsAbDirectoryQuery::nsAbDirectoryQuery()
+{
+}
+
+nsAbDirectoryQuery::~nsAbDirectoryQuery()
+{
+}
+
+NS_IMETHODIMP nsAbDirectoryQuery::DoQuery(nsIAbDirectory *aDirectory,
+ nsIAbDirectoryQueryArguments* arguments,
+ nsIAbDirSearchListener* listener,
+ int32_t resultLimit, int32_t timeOut,
+ int32_t* _retval)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ nsCOMPtr<nsISupports> supportsExpression;
+ nsresult rv = arguments->GetExpression(getter_AddRefs(supportsExpression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression(do_QueryInterface(supportsExpression, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool doSubDirectories;
+ rv = arguments->GetQuerySubDirectories(&doSubDirectories);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = query(aDirectory, expression, listener, doSubDirectories, &resultLimit);
+
+ rv = NS_FAILED(rv) ? queryError(listener) : queryFinished(listener);
+
+ *_retval = 0;
+ return rv;
+}
+
+/* void stopQuery (in long contextID); */
+NS_IMETHODIMP nsAbDirectoryQuery::StopQuery(int32_t contextID)
+{
+ return NS_OK;
+}
+
+
+nsresult nsAbDirectoryQuery::query(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit)
+{
+ if (*resultLimit == 0)
+ return NS_OK;
+
+ nsresult rv = queryCards(directory, expression, listener, resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (doSubDirectories && resultLimit != 0)
+ {
+ rv = queryChildren(directory, expression, listener, doSubDirectories,
+ resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsresult nsAbDirectoryQuery::queryChildren(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsISimpleEnumerator> subDirectories;
+ rv = directory->GetChildNodes(getter_AddRefs(subDirectories));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = subDirectories->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = subDirectories->GetNext (getter_AddRefs (item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> subDirectory(do_QueryInterface(item, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = query(subDirectory, expression, listener, doSubDirectories, resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ }
+ return NS_OK;
+}
+
+nsresult nsAbDirectoryQuery::queryCards(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsISimpleEnumerator> cards;
+ rv = directory->GetChildCards(getter_AddRefs(cards));
+ if (NS_FAILED(rv))
+ {
+ if (rv != NS_ERROR_NOT_IMPLEMENTED)
+ NS_ENSURE_SUCCESS(rv, rv);
+ else
+ return NS_OK;
+ }
+
+ if (!cards)
+ return NS_OK;
+
+ bool more;
+ while (NS_SUCCEEDED(cards->HasMoreElements(&more)) && more)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = cards->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(item, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = matchCard (card, expression, listener, resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*resultLimit == 0)
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAbDirectoryQuery::matchCard(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit)
+{
+ bool matchFound = false;
+ nsresult rv = matchCardExpression(card, expression, &matchFound);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matchFound)
+ {
+ (*resultLimit)--;
+ rv = queryMatch(card, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsresult nsAbDirectoryQuery::matchCardExpression(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ bool* result)
+{
+ nsAbBooleanOperationType operation;
+ nsresult rv = expression->GetOperation (&operation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIArray> childExpressions;
+ rv = expression->GetExpressions (getter_AddRefs (childExpressions));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ rv = childExpressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (operation == nsIAbBooleanOperationTypes::NOT &&
+ count > 1)
+ return NS_ERROR_FAILURE;
+
+ bool value = *result = false;
+ nsCOMPtr<nsIAbBooleanConditionString> childCondition;
+ nsCOMPtr<nsIAbBooleanExpression> childExpression;
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ childCondition = do_QueryElementAt(childExpressions, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = matchCardCondition (card, childCondition, &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ childExpression = do_QueryElementAt(childExpressions, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = matchCardExpression (card, childExpression, &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ return NS_ERROR_FAILURE;
+ }
+ if (operation == nsIAbBooleanOperationTypes::OR && value)
+ break;
+ else if (operation == nsIAbBooleanOperationTypes::AND && !value)
+ break;
+ else if (operation == nsIAbBooleanOperationTypes::NOT)
+ value = !value;
+ }
+ *result = value;
+
+ return NS_OK;
+}
+
+nsresult nsAbDirectoryQuery::matchCardCondition(nsIAbCard* card,
+ nsIAbBooleanConditionString* condition,
+ bool* matchFound)
+{
+ nsAbBooleanConditionType conditionType;
+ nsresult rv = condition->GetCondition (&conditionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString name;
+ rv = condition->GetName (getter_Copies (name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (name.Equals ("card:nsIAbCard"))
+ {
+ *matchFound = (conditionType == nsIAbBooleanConditionTypes::Exists);
+ return NS_OK;
+ }
+
+ nsString matchValue;
+ rv = condition->GetValue (getter_Copies (matchValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (name.EqualsLiteral("IsMailList"))
+ {
+ bool isMailList;
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only equals is supported.
+ if (conditionType != nsIAbBooleanConditionTypes::Is)
+ return NS_ERROR_FAILURE;
+
+ *matchFound = isMailList ? matchValue.EqualsLiteral("TRUE") :
+ matchValue.EqualsLiteral("FALSE");
+ return NS_OK;
+ }
+
+ nsString value;
+ (void)card->GetPropertyAsAString(name.get(), value);
+
+ if (value.IsEmpty())
+ {
+ *matchFound = (conditionType == nsIAbBooleanConditionTypes::DoesNotExist) ?
+ true : false;
+ return NS_OK;
+ }
+
+ /* TODO
+ * What about allowing choice between case insensitive
+ * and case sensitive comparisons?
+ *
+ */
+ switch (conditionType)
+ {
+ case nsIAbBooleanConditionTypes::Exists:
+ *matchFound = true;
+ break;
+ case nsIAbBooleanConditionTypes::Contains:
+ *matchFound = CaseInsensitiveFindInReadable(matchValue, value);
+ break;
+ case nsIAbBooleanConditionTypes::DoesNotContain:
+ *matchFound = !CaseInsensitiveFindInReadable(matchValue, value);
+ break;
+ case nsIAbBooleanConditionTypes::Is:
+ *matchFound = value.Equals(matchValue, nsCaseInsensitiveStringComparator());
+ break;
+ case nsIAbBooleanConditionTypes::IsNot:
+ *matchFound = !value.Equals(matchValue, nsCaseInsensitiveStringComparator());
+ break;
+ case nsIAbBooleanConditionTypes::BeginsWith:
+ *matchFound = StringBeginsWith(value, matchValue, nsCaseInsensitiveStringComparator());
+ break;
+ case nsIAbBooleanConditionTypes::LessThan:
+ *matchFound = Compare(value, matchValue, nsCaseInsensitiveStringComparator()) < 0;
+ break;
+ case nsIAbBooleanConditionTypes::GreaterThan:
+ *matchFound = Compare(value, matchValue, nsCaseInsensitiveStringComparator()) > 0;
+ break;
+ case nsIAbBooleanConditionTypes::EndsWith:
+ *matchFound = StringEndsWith(value, matchValue, nsCaseInsensitiveStringComparator());
+ break;
+ case nsIAbBooleanConditionTypes::SoundsLike:
+ case nsIAbBooleanConditionTypes::RegExp:
+ *matchFound = false;
+ break;
+ default:
+ *matchFound = false;
+ }
+
+ return rv;
+}
+
+nsresult nsAbDirectoryQuery::queryMatch(nsIAbCard* card,
+ nsIAbDirSearchListener* listener)
+{
+ return listener->OnSearchFoundCard(card);
+}
+
+nsresult nsAbDirectoryQuery::queryFinished(nsIAbDirSearchListener* listener)
+{
+ return listener->OnSearchFinished(nsIAbDirectoryQueryResultListener::queryResultComplete, EmptyString());
+}
+
+nsresult nsAbDirectoryQuery::queryError(nsIAbDirSearchListener* listener)
+{
+ return listener->OnSearchFinished(nsIAbDirectoryQueryResultListener::queryResultError, EmptyString());
+}
diff --git a/mailnews/addrbook/src/nsAbDirectoryQuery.h b/mailnews/addrbook/src/nsAbDirectoryQuery.h
new file mode 100644
index 000000000..99aa943aa
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirectoryQuery.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbDirectoryQuery_h__
+#define nsAbDirectoryQuery_h__
+
+#include "nsIAbDirectoryQuery.h"
+#include "nsIAbDirectory.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIArray.h"
+#include "nsIAbBooleanExpression.h"
+
+class nsAbDirectoryQuerySimpleBooleanExpression : public nsIAbBooleanExpression
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABBOOLEANEXPRESSION
+
+ nsAbDirectoryQuerySimpleBooleanExpression();
+
+private:
+ virtual ~nsAbDirectoryQuerySimpleBooleanExpression();
+
+public:
+ nsCOMPtr<nsIArray> mExpressions;
+ nsAbBooleanOperationType mOperation;
+};
+
+
+class nsAbDirectoryQueryArguments : public nsIAbDirectoryQueryArguments
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERYARGUMENTS
+
+ nsAbDirectoryQueryArguments();
+
+private:
+ virtual ~nsAbDirectoryQueryArguments();
+
+protected:
+ nsCOMPtr<nsISupports> mExpression;
+ nsCOMPtr<nsISupports> mTypeSpecificArg;
+ bool mQuerySubDirectories;
+ nsCString mFilter;
+};
+
+
+class nsAbDirectoryQueryPropertyValue : public nsIAbDirectoryQueryPropertyValue
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERYPROPERTYVALUE
+
+ nsAbDirectoryQueryPropertyValue();
+ nsAbDirectoryQueryPropertyValue(const char* aName,
+ const char16_t* aValue);
+ nsAbDirectoryQueryPropertyValue(const char* aName,
+ nsISupports* aValueISupports);
+
+protected:
+ virtual ~nsAbDirectoryQueryPropertyValue();
+ nsCString mName;
+ nsString mValue;
+ nsCOMPtr<nsISupports> mValueISupports;
+};
+
+
+class nsAbDirectoryQuery : public nsIAbDirectoryQuery
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERY
+
+ nsAbDirectoryQuery();
+
+protected:
+ virtual ~nsAbDirectoryQuery();
+ nsresult query(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit);
+ nsresult queryChildren(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit);
+ nsresult queryCards(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit);
+ nsresult matchCard(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit);
+ nsresult matchCardExpression(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ bool* result);
+ nsresult matchCardCondition(nsIAbCard* card,
+ nsIAbBooleanConditionString* condition,
+ bool* matchFound);
+
+ nsresult queryMatch (nsIAbCard* card,
+ nsIAbDirSearchListener* listener);
+ nsresult queryFinished(nsIAbDirSearchListener* listener);
+ nsresult queryError(nsIAbDirSearchListener* listener);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp
new file mode 100644
index 000000000..553f3b903
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbDirectoryQuery.h"
+#include "nsAbDirectoryQueryProxy.h"
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQueryProxy, nsIAbDirectoryQueryProxy, nsIAbDirectoryQuery)
+
+nsAbDirectoryQueryProxy::nsAbDirectoryQueryProxy() :
+ mInitiated (false)
+{
+}
+
+nsAbDirectoryQueryProxy::~nsAbDirectoryQueryProxy()
+{
+}
+
+/* void initiate (in nsIAbDirectory directory); */
+NS_IMETHODIMP nsAbDirectoryQueryProxy::Initiate()
+{
+ if (mInitiated)
+ return NS_OK;
+
+ mDirectoryQuery = new nsAbDirectoryQuery();
+
+ mInitiated = true;
+
+ return NS_OK;
+}
+
+
diff --git a/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h
new file mode 100644
index 000000000..89a323c75
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbDirectoryQueryProxy_h__
+#define nsAbDirectoryQueryProxy_h__
+
+#include "nsIAbDirectoryQueryProxy.h"
+#include "nsCOMPtr.h"
+
+class nsAbDirectoryQueryProxy : public nsIAbDirectoryQueryProxy
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIABDIRECTORYQUERY(mDirectoryQuery->)
+ NS_DECL_NSIABDIRECTORYQUERYPROXY
+
+ nsAbDirectoryQueryProxy();
+
+protected:
+ virtual ~nsAbDirectoryQueryProxy();
+ bool mInitiated;
+ nsCOMPtr<nsIAbDirectoryQuery> mDirectoryQuery;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPAttributeMap.js b/mailnews/addrbook/src/nsAbLDAPAttributeMap.js
new file mode 100644
index 000000000..8ff2406d2
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPAttributeMap.js
@@ -0,0 +1,247 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var NS_ABLDAPATTRIBUTEMAP_CID = Components.ID(
+ "{127b341a-bdda-4270-85e1-edff569a9b85}");
+var NS_ABLDAPATTRIBUTEMAPSERVICE_CID = Components.ID(
+ "{4ed7d5e1-8800-40da-9e78-c4f509d7ac5e}");
+
+function nsAbLDAPAttributeMap() {
+ this.mPropertyMap = {};
+ this.mAttrMap = {};
+}
+
+nsAbLDAPAttributeMap.prototype = {
+ classID: NS_ABLDAPATTRIBUTEMAP_CID,
+
+ getAttributeList: function getAttributeList(aProperty) {
+
+ if (!(aProperty in this.mPropertyMap)) {
+ return null;
+ }
+
+ // return the joined list
+ return this.mPropertyMap[aProperty].join(",");
+ },
+
+ getAttributes: function getAttributes(aProperty, aCount, aAttrs) {
+
+ // fail if no entry for this
+ if (!(aProperty in this.mPropertyMap)) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ aAttrs = this.mPropertyMap[aProperty];
+ aCount = aAttrs.length;
+ return aAttrs;
+ },
+
+ getFirstAttribute: function getFirstAttribute(aProperty) {
+
+ // fail if no entry for this
+ if (!(aProperty in this.mPropertyMap)) {
+ return null;
+ }
+
+ return this.mPropertyMap[aProperty][0];
+ },
+
+ setAttributeList: function setAttributeList(aProperty, aAttributeList,
+ aAllowInconsistencies) {
+
+ var attrs = aAttributeList.split(",");
+
+ // check to make sure this call won't allow multiple mappings to be
+ // created, if requested
+ if (!aAllowInconsistencies) {
+ for (var attr of attrs) {
+ if (attr in this.mAttrMap && this.mAttrMap[attr] != aProperty) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ // delete any attr mappings created by the existing property map entry
+ if (aProperty in this.mPropertyMap) {
+ for (attr of this.mPropertyMap[aProperty]) {
+ delete this.mAttrMap[attr];
+ }
+ }
+
+ // add these attrs to the attrmap
+ for (attr of attrs) {
+ this.mAttrMap[attr] = aProperty;
+ }
+
+ // add them to the property map
+ this.mPropertyMap[aProperty] = attrs;
+ },
+
+ getProperty: function getProperty(aAttribute) {
+
+ if (!(aAttribute in this.mAttrMap)) {
+ return null;
+ }
+
+ return this.mAttrMap[aAttribute];
+ },
+
+ getAllCardAttributes: function getAllCardAttributes() {
+ var attrs = [];
+ for (var prop in this.mPropertyMap) {
+ let attrArray = this.mPropertyMap[prop];
+ attrs = attrs.concat(attrArray);
+ }
+
+ if (!attrs.length) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ return attrs.join(",");
+ },
+
+ getAllCardProperties: function getAllCardProperties(aCount) {
+
+ var props = [];
+ for (var prop in this.mPropertyMap) {
+ props.push(prop);
+ }
+
+ aCount.value = props.length;
+ return props;
+ },
+
+ setFromPrefs: function setFromPrefs(aPrefBranchName) {
+ // get the right pref branch
+ let branch = Services.prefs.getBranch(aPrefBranchName + ".");
+
+ // get the list of children
+ var childCount = {};
+ var children = branch.getChildList("", childCount);
+
+ // do the actual sets
+ for (var child of children) {
+ this.setAttributeList(child, branch.getCharPref(child), true);
+ }
+
+ // ensure that everything is kosher
+ this.checkState();
+ },
+
+ setCardPropertiesFromLDAPMessage: function
+ setCardPropertiesFromLDAPMessage(aMessage, aCard) {
+
+ var cardValueWasSet = false;
+
+ var msgAttrCount = {};
+ var msgAttrs = aMessage.getAttributes(msgAttrCount);
+
+ // downcase the array for comparison
+ function toLower(a) { return a.toLowerCase(); }
+ msgAttrs = msgAttrs.map(toLower);
+
+ // deal with each addressbook property
+ for (var prop in this.mPropertyMap) {
+
+ // go through the list of possible attrs in precedence order
+ for (var attr of this.mPropertyMap[prop]) {
+
+ attr = attr.toLowerCase();
+
+ // find the first attr that exists in this message
+ if (msgAttrs.indexOf(attr) != -1) {
+
+ try {
+ var values = aMessage.getValues(attr, {});
+ // strip out the optional label from the labeledURI
+ if (attr == "labeleduri" && values[0]) {
+ var index = values[0].indexOf(" ");
+ if (index != -1)
+ values[0] = values[0].substring(0, index);
+ }
+ aCard.setProperty(prop, values[0]);
+
+ cardValueWasSet = true;
+ break;
+ } catch (ex) {
+ // ignore any errors getting message values or setting card values
+ }
+ }
+ }
+ }
+
+ if (!cardValueWasSet) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ return;
+ },
+
+ checkState: function checkState() {
+
+ var attrsSeen = [];
+
+ for (var prop in this.mPropertyMap) {
+ let attrArray = this.mPropertyMap[prop];
+ for (var attr of attrArray) {
+
+ // multiple attributes that mapped to the empty string are permitted
+ if (!attr.length) {
+ continue;
+ }
+
+ // if we've seen this before, there's a problem
+ if (attrsSeen.indexOf(attr) != -1) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ // remember that we've seen it now
+ attrsSeen.push(attr);
+ }
+ }
+
+ return;
+ },
+
+ QueryInterface: XPCOMUtils
+ .generateQI([Components.interfaces.nsIAbLDAPAttributeMap])
+}
+
+function nsAbLDAPAttributeMapService() {
+}
+
+nsAbLDAPAttributeMapService.prototype = {
+
+ classID: NS_ABLDAPATTRIBUTEMAPSERVICE_CID,
+
+ mAttrMaps: {},
+
+ getMapForPrefBranch: function getMapForPrefBranch(aPrefBranchName) {
+
+ // if we've already got this map, return it
+ if (aPrefBranchName in this.mAttrMaps) {
+ return this.mAttrMaps[aPrefBranchName];
+ }
+
+ // otherwise, try and create it
+ var attrMap = new nsAbLDAPAttributeMap();
+ attrMap.setFromPrefs("ldap_2.servers.default.attrmap");
+ attrMap.setFromPrefs(aPrefBranchName + ".attrmap");
+
+ // cache
+ this.mAttrMaps[aPrefBranchName] = attrMap;
+
+ // and return
+ return attrMap;
+ },
+
+ QueryInterface: XPCOMUtils
+ .generateQI([Components.interfaces.nsIAbLDAPAttributeMapService])
+}
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsAbLDAPAttributeMap, nsAbLDAPAttributeMapService]);
+
diff --git a/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js b/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js
new file mode 100644
index 000000000..1c62d7e97
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js
@@ -0,0 +1,325 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var ACR = Components.interfaces.nsIAutoCompleteResult;
+var nsIAbAutoCompleteResult = Components.interfaces.nsIAbAutoCompleteResult;
+var nsIAbDirectoryQueryResultListener =
+ Components.interfaces.nsIAbDirectoryQueryResultListener;
+
+// nsAbLDAPAutoCompleteResult
+// Derived from nsIAbAutoCompleteResult, provides a LDAP specific result
+// implementation.
+
+function nsAbLDAPAutoCompleteResult(aSearchString) {
+ // Can't create this in the prototype as we'd get the same array for
+ // all instances
+ this._searchResults = [];
+ this.searchString = aSearchString;
+}
+
+nsAbLDAPAutoCompleteResult.prototype = {
+ _searchResults: null,
+ _commentColumn: "",
+
+ // nsIAutoCompleteResult
+
+ searchString: null,
+ searchResult: ACR.RESULT_NOMATCH,
+ defaultIndex: -1,
+ errorDescription: null,
+
+ get matchCount() {
+ return this._searchResults.length;
+ },
+
+ getLabelAt: function getLabelAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ getValueAt: function getValueAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getCommentAt: function getCommentAt(aIndex) {
+ return this._commentColumn;
+ },
+
+ getStyleAt: function getStyleAt(aIndex) {
+ return this.searchResult == ACR.RESULT_FAILURE ? "remote-err" :
+ "remote-abook";
+ },
+
+ getImageAt: function getImageAt(aIndex) {
+ return "";
+ },
+
+ getFinalCompleteValueAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) {
+ },
+
+ // nsIAbAutoCompleteResult
+
+ getCardAt: function getCardAt(aIndex) {
+ return this._searchResults[aIndex].card;
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([ACR, nsIAbAutoCompleteResult])
+}
+
+function nsAbLDAPAutoCompleteSearch() {
+ Services.obs.addObserver(this, "quit-application", false);
+ this._timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+}
+
+nsAbLDAPAutoCompleteSearch.prototype = {
+ // For component registration
+ classID: Components.ID("227e6482-fe9f-441f-9b7d-7b60375e7449"),
+
+ // A short-lived LDAP directory cache.
+ // To avoid recreating components as the user completes, we maintain the most
+ // recently used address book, nsAbLDAPDirectoryQuery and search context.
+ // However the cache is discarded if it has not been used for a minute.
+ // This is done to avoid problems with LDAP sessions timing out and hanging.
+ _query: null,
+ _book: null,
+ _attributes: null,
+ _context: -1,
+ _timer: null,
+
+ // The current search result.
+ _result: null,
+ // The listener to pass back results to.
+ _listener: null,
+
+ _parser: MailServices.headerParser,
+
+ applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
+ // Private methods
+
+ _checkDuplicate: function _checkDuplicate(card, emailAddress) {
+ var lcEmailAddress = emailAddress.toLocaleLowerCase();
+
+ return this._result._searchResults.some(function(result) {
+ return result.value.toLocaleLowerCase() == lcEmailAddress;
+ });
+ },
+
+ _addToResult: function(card) {
+ let mbox = this._parser.makeMailboxObject(card.displayName,
+ card.isMailList ? card.getProperty("Notes", "") || card.displayName :
+ card.primaryEmail);
+ if (!mbox.email)
+ return;
+
+ let emailAddress = mbox.toString();
+
+ // If it is a duplicate, then just return and don't add it. The
+ // _checkDuplicate function deals with it all for us.
+ if (this._checkDuplicate(card, emailAddress))
+ return;
+
+ // Find out where to insert the card.
+ var insertPosition = 0;
+
+ // Next sort on full address
+ while (insertPosition < this._result._searchResults.length &&
+ emailAddress > this._result._searchResults[insertPosition].value)
+ ++insertPosition;
+
+ this._result._searchResults.splice(insertPosition, 0, {
+ value: emailAddress,
+ card: card,
+ });
+ },
+
+ // nsIObserver
+
+ observe: function observer(subject, topic, data) {
+ if (topic == "quit-application") {
+ Services.obs.removeObserver(this, "quit-application");
+ } else if (topic != "timer-callback") {
+ return;
+ }
+
+ // Force the individual query items to null, so that the memory
+ // gets collected straight away.
+ this.stopSearch();
+ this._book = null;
+ this._context = -1;
+ this._query = null;
+ this._attributes = null;
+ },
+
+ // nsIAutoCompleteSearch
+
+ startSearch: function startSearch(aSearchString, aParam,
+ aPreviousResult, aListener) {
+ let params = JSON.parse(aParam) || {};
+ let applicable = !("type" in params) || this.applicableHeaders.has(params.type);
+
+ this._result = new nsAbLDAPAutoCompleteResult(aSearchString);
+ aSearchString = aSearchString.toLocaleLowerCase();
+
+ // If the search string isn't value, or contains a comma, or the user
+ // hasn't enabled autocomplete, then just return no matches / or the
+ // result ignored.
+ // The comma check is so that we don't autocomplete against the user
+ // entering multiple addresses.
+ if (!applicable || !aSearchString || aSearchString.includes(",")) {
+ this._result.searchResult = ACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, this._result);
+ return;
+ }
+
+ // The rules here: If the current identity has a directoryServer set, then
+ // use that, otherwise, try the global preference instead.
+ var acDirURI = null;
+ var identity;
+
+ if ("idKey" in params) {
+ try {
+ identity = MailServices.accounts.getIdentity(params.idKey);
+ }
+ catch(ex) {
+ Components.utils.reportError("Couldn't get specified identity, " +
+ "falling back to global settings");
+ }
+ }
+
+ // Does the current identity override the global preference?
+ if (identity && identity.overrideGlobalPref)
+ acDirURI = identity.directoryServer;
+ else {
+ // Try the global one
+ if (Services.prefs.getBoolPref("ldap_2.autoComplete.useDirectory"))
+ acDirURI = Services.prefs.getCharPref("ldap_2.autoComplete.directoryServer");
+ }
+
+ if (!acDirURI) {
+ // No directory to search, send a no match and return.
+ aListener.onSearchResult(this, this._result);
+ return;
+ }
+
+ this.stopSearch();
+
+ // If we don't already have a cached query for this URI, build a new one.
+ acDirURI = "moz-abldapdirectory://" + acDirURI;
+ if (!this._book || this._book.URI != acDirURI) {
+ this._query =
+ Components.classes["@mozilla.org/addressbook/ldap-directory-query;1"]
+ .createInstance(Components.interfaces.nsIAbDirectoryQuery);
+ this._book = MailServices.ab.getDirectory(acDirURI)
+ .QueryInterface(Components.interfaces.nsIAbLDAPDirectory);
+
+ // Create a minimal map just for the display name and primary email.
+ this._attributes =
+ Components.classes["@mozilla.org/addressbook/ldap-attribute-map;1"]
+ .createInstance(Components.interfaces.nsIAbLDAPAttributeMap);
+ this._attributes.setAttributeList("DisplayName",
+ this._book.attributeMap.getAttributeList("DisplayName", {}), true);
+ this._attributes.setAttributeList("PrimaryEmail",
+ this._book.attributeMap.getAttributeList("PrimaryEmail", {}), true);
+ }
+
+ this._result._commentColumn = this._book.dirName;
+ this._listener = aListener;
+ this._timer.init(this, 60000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+
+ var args =
+ Components.classes["@mozilla.org/addressbook/directory/query-arguments;1"]
+ .createInstance(Components.interfaces.nsIAbDirectoryQueryArguments);
+
+ var filterTemplate = this._book.getStringValue("autoComplete.filterTemplate", "");
+
+ // Use default value when preference is not set or it contains empty string
+ if (!filterTemplate)
+ filterTemplate = "(|(cn=%v1*%v2-*)(mail=%v1*%v2-*)(sn=%v1*%v2-*))";
+
+ // Create filter from filter template and search string
+ var ldapSvc = Components.classes["@mozilla.org/network/ldap-service;1"]
+ .getService(Components.interfaces.nsILDAPService);
+ var filter = ldapSvc.createFilter(1024, filterTemplate, "", "", "", aSearchString);
+ if (!filter)
+ throw new Error("Filter string is empty, check if filterTemplate variable is valid in prefs.js.");
+ args.typeSpecificArg = this._attributes;
+ args.querySubDirectories = true;
+ args.filter = filter;
+
+ // Start the actual search
+ this._context =
+ this._query.doQuery(this._book, args, this, this._book.maxHits, 0);
+ },
+
+ stopSearch: function stopSearch() {
+ if (this._listener) {
+ this._query.stopQuery(this._context);
+ this._listener = null;
+ }
+ },
+
+ // nsIAbDirSearchListener
+
+ onSearchFinished: function onSearchFinished(aResult, aErrorMsg) {
+ if (!this._listener)
+ return;
+
+ if (aResult == nsIAbDirectoryQueryResultListener.queryResultComplete) {
+ if (this._result.matchCount) {
+ this._result.searchResult = ACR.RESULT_SUCCESS;
+ this._result.defaultIndex = 0;
+ }
+ else
+ this._result.searchResult = ACR.RESULT_NOMATCH;
+ }
+ else if (aResult == nsIAbDirectoryQueryResultListener.queryResultError) {
+ this._result.searchResult = ACR.RESULT_FAILURE;
+ this._result.defaultIndex = 0;
+ }
+ // const long queryResultStopped = 2;
+ // const long queryResultError = 3;
+ this._listener.onSearchResult(this, this._result);
+ this._listener = null;
+ },
+
+ onSearchFoundCard: function onSearchFoundCard(aCard) {
+ if (!this._listener)
+ return;
+
+ this._addToResult(aCard);
+
+ /* XXX autocomplete doesn't expect you to rearrange while searching
+ if (this._result.matchCount)
+ this._result.searchResult = ACR.RESULT_SUCCESS_ONGOING;
+ else
+ this._result.searchResult = ACR.RESULT_NOMATCH_ONGOING;
+
+ this._listener.onSearchResult(this, this._result);
+ */
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver,
+ Components.interfaces
+ .nsIAutoCompleteSearch,
+ Components.interfaces
+ .nsIAbDirSearchListener])
+};
+
+// Module
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsAbLDAPAutoCompleteSearch]);
diff --git a/mailnews/addrbook/src/nsAbLDAPCard.cpp b/mailnews/addrbook/src/nsAbLDAPCard.cpp
new file mode 100644
index 000000000..6dcfdedbb
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPCard.cpp
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPCard.h"
+#include "nsIMutableArray.h"
+#include "nsCOMPtr.h"
+#include "nsILDAPModification.h"
+#include "nsILDAPBERValue.h"
+#include "nsILDAPMessage.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsAbBaseCID.h"
+#include "nsAbUtils.h"
+#include "nsILDAPErrors.h"
+
+#include <stdio.h>
+
+#define kDNColumn "DN"
+
+nsAbLDAPCard::nsAbLDAPCard()
+{
+}
+
+nsAbLDAPCard::~nsAbLDAPCard()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbLDAPCard, nsAbCardProperty, nsIAbLDAPCard)
+
+/* Retrieves the changes to the LDAP card and stores them in an LDAP
+ * update message.
+ *
+ * Calling this method changes the LDAP card, it updates the
+ * meta-properties (m_*) to reflect what the LDAP contents will be once
+ * the update has been performed. This allows you to do multiple (successful)
+ * consecutive edits on a card in a search result. If the meta-properties
+ * were not updated, incorrect assuptions would be made about what object
+ * classes to add, or what attributes to clear.
+ *
+ * XXX: We need to take care when integrating this code with the asynchronous
+ * update dialogs, as the current code in nsAbLDAPDirectory has a problem
+ * when an update fails: the modified card still gets stored and shown to
+ * the user instead of being discarded. There is one especially tricky case:
+ * when you do an update on a card which changes its DN, you have two
+ * operations (rename, then update the other attributes). If the rename
+ * operation succeeds and not the update of the attributes, you are
+ * "somewhere in between" the original card and the updated card.
+*/
+NS_IMETHODIMP nsAbLDAPCard::GetLDAPMessageInfo(
+ nsIAbLDAPAttributeMap *aAttributeMap,
+ const uint32_t aClassCount,
+ const char **aClasses,
+ int32_t aType,
+ nsIArray **aLDAPAddMessageInfo)
+{
+ NS_ENSURE_ARG_POINTER(aAttributeMap);
+ NS_ENSURE_ARG_POINTER(aClasses);
+ NS_ENSURE_ARG_POINTER(aLDAPAddMessageInfo);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> modArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add any missing object classes. We never remove any object
+ // classes: if an entry has additional object classes, it's probably
+ // for a good reason.
+ nsAutoCString oclass;
+ for (uint32_t i = 0; i < aClassCount; ++i)
+ {
+ oclass.Assign(nsDependentCString(aClasses[i]));
+ ToLowerCase(oclass);
+
+ if (!m_objectClass.Contains(oclass))
+ {
+ m_objectClass.AppendElement(oclass);
+ printf("LDAP : adding objectClass %s\n", oclass.get());
+ }
+ }
+
+ nsCOMPtr<nsILDAPModification> mod =
+ do_CreateInstance("@mozilla.org/network/ldap-modification;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> values =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < m_objectClass.Length(); ++i)
+ {
+ nsCOMPtr<nsILDAPBERValue> value =
+ do_CreateInstance("@mozilla.org/network/ldap-ber-value;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = value->SetFromUTF8(m_objectClass.ElementAt(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = values->AppendElement(value, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mod->SetUpModification(aType, NS_LITERAL_CSTRING("objectClass"), values);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ modArray->AppendElement(mod, false);
+
+ // Add card properties
+ CharPtrArrayGuard props;
+ rv = aAttributeMap->GetAllCardProperties(props.GetSizeAddr(),
+ props.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString attr;
+ nsCString propvalue;
+ for (uint32_t i = 0; i < props.GetSize(); ++i)
+ {
+ // Skip some attributes that don't map to LDAP.
+ //
+ // BirthYear : by default this is mapped to 'birthyear',
+ // which is not part of mozillaAbPersonAlpha
+ //
+ // LastModifiedDate : by default this is mapped to 'modifytimestamp',
+ // which cannot be modified
+ //
+ // PreferMailFormat : by default this is mapped to 'mozillaUseHtmlMail',
+ // which is a boolean, not plaintext/html/unknown
+ if (!strcmp(props[i], kBirthYearProperty) ||
+ !strcmp(props[i], kLastModifiedDateProperty) ||
+ !strcmp(props[i], kPreferMailFormatProperty))
+ continue;
+
+ rv = aAttributeMap->GetFirstAttribute(nsDependentCString(props[i]),
+ attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ToLowerCase(attr);
+
+ // If the property is not mapped to an attribute, skip it.
+ if (attr.IsEmpty())
+ continue;
+
+ nsCOMPtr<nsILDAPModification> mod =
+ do_CreateInstance("@mozilla.org/network/ldap-modification;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ size_t index = m_attributes.IndexOf(attr);
+
+ rv = GetPropertyAsAUTF8String(props[i], propvalue);
+
+ if (NS_SUCCEEDED(rv) &&!propvalue.IsEmpty())
+ {
+ // If the new value is not empty, add/update it
+ nsCOMPtr<nsILDAPBERValue> value =
+ do_CreateInstance("@mozilla.org/network/ldap-ber-value;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = value->SetFromUTF8(propvalue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mod->SetUpModificationOneValue(aType, attr, value);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ printf("LDAP : setting attribute %s (%s) to '%s'\n", attr.get(),
+ props[i], propvalue.get());
+ modArray->AppendElement(mod, false);
+ if (index != m_attributes.NoIndex)
+ m_attributes.AppendElement(attr);
+
+ }
+ else if (aType == nsILDAPModification::MOD_REPLACE &&
+ index != m_attributes.NoIndex)
+ {
+ // If the new value is empty, we are performing an update
+ // and the attribute was previously set, clear it
+ nsCOMPtr<nsIMutableArray> novalues =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mod->SetUpModification(aType, attr, novalues);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ printf("LDAP : removing attribute %s (%s)\n", attr.get(), props[i]);
+ modArray->AppendElement(mod, false);
+ m_attributes.RemoveElementAt(index);
+ }
+ }
+
+ NS_ADDREF(*aLDAPAddMessageInfo = modArray);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPCard::BuildRdn(nsIAbLDAPAttributeMap *aAttributeMap,
+ const uint32_t aAttrCount,
+ const char **aAttributes,
+ nsACString &aRdn)
+{
+ NS_ENSURE_ARG_POINTER(aAttributeMap);
+ NS_ENSURE_ARG_POINTER(aAttributes);
+
+ nsresult rv;
+ nsCString attr;
+ nsAutoCString prop;
+ nsCString propvalue;
+
+ aRdn.Truncate();
+ for (uint32_t i = 0; i < aAttrCount; ++i)
+ {
+ attr.Assign(nsDependentCString(aAttributes[i]));
+
+ // Lookup the property corresponding to the attribute
+ rv = aAttributeMap->GetProperty(attr, prop);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the property value
+ rv = GetPropertyAsAUTF8String(prop.get(), propvalue);
+
+ // XXX The case where an attribute needed to build the Relative
+ // Distinguished Name is not set needs to be handled by the caller,
+ // so as to let the user know what is missing.
+ if (NS_FAILED(rv) || propvalue.IsEmpty())
+ {
+ NS_ERROR("nsAbLDAPCard::BuildRdn: a required attribute is not set");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ aRdn.Append(attr);
+ aRdn.AppendLiteral("=");
+ aRdn.Append(propvalue);
+ if (i < aAttrCount - 1)
+ aRdn.AppendLiteral("+");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPCard::GetDn(nsACString &aDN)
+{
+ return GetPropertyAsAUTF8String(kDNColumn, aDN);
+}
+
+NS_IMETHODIMP nsAbLDAPCard::SetDn(const nsACString &aDN)
+{
+ SetLocalId(aDN);
+ return SetPropertyAsAUTF8String(kDNColumn, aDN);
+}
+
+NS_IMETHODIMP nsAbLDAPCard::SetMetaProperties(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ // Get DN
+ nsAutoCString dn;
+ nsresult rv = aMessage->GetDn(dn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetDn(dn);
+
+ // Get the list of set attributes
+ CharPtrArrayGuard attrs;
+ rv = aMessage->GetAttributes(attrs.GetSizeAddr(), attrs.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString attr;
+ m_attributes.Clear();
+ for (uint32_t i = 0; i < attrs.GetSize(); ++i)
+ {
+ attr.Assign(nsDependentCString(attrs[i]));
+ ToLowerCase(attr);
+ m_attributes.AppendElement(attr);
+ }
+
+ // Get the objectClass values
+ m_objectClass.Clear();
+ PRUnicharPtrArrayGuard vals;
+ rv = aMessage->GetValues("objectClass", vals.GetSizeAddr(),
+ vals.GetArrayAddr());
+
+ // objectClass is not always included in search result entries and
+ // nsILDAPMessage::GetValues returns NS_ERROR_LDAP_DECODING_ERROR if the
+ // requested attribute doesn't exist.
+ if (rv == NS_ERROR_LDAP_DECODING_ERROR)
+ return NS_OK;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString oclass;
+ for (uint32_t i = 0; i < vals.GetSize(); ++i)
+ {
+ oclass.Assign(NS_LossyConvertUTF16toASCII(nsDependentString(vals[i])));
+ ToLowerCase(oclass);
+ m_objectClass.AppendElement(oclass);
+ }
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPCard.h b/mailnews/addrbook/src/nsAbLDAPCard.h
new file mode 100644
index 000000000..ef11b50e4
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPCard.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPCard_h__
+#define nsAbLDAPCard_h__
+
+#include "nsAbCardProperty.h"
+#include "nsIAbLDAPCard.h"
+#include "nsTArray.h"
+
+class nsIMutableArray;
+
+class nsAbLDAPCard : public nsAbCardProperty,
+ public nsIAbLDAPCard
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIABLDAPCARD
+
+ nsAbLDAPCard();
+
+protected:
+ virtual ~nsAbLDAPCard();
+ nsTArray<nsCString> m_attributes;
+ nsTArray<nsCString> m_objectClass;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp b/mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp
new file mode 100644
index 000000000..cc4c04250
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp
@@ -0,0 +1,542 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPChangeLogData.h"
+#include "nsAbLDAPChangeLogQuery.h"
+#include "nsILDAPMessage.h"
+#include "nsIAbCard.h"
+#include "nsIAddrBookSession.h"
+#include "nsAbBaseCID.h"
+#include "nsAbUtils.h"
+#include "nsAbMDBCard.h"
+#include "nsAbLDAPCard.h"
+#include "nsIAuthPrompt.h"
+#include "nsIStringBundle.h"
+#include "nsIWindowWatcher.h"
+#include "nsUnicharUtils.h"
+#include "plstr.h"
+#include "nsILDAPErrors.h"
+#include "prmem.h"
+#include "mozilla/Services.h"
+
+// Defined here since to be used
+// only locally to this file.
+enum UpdateOp {
+ NO_OP,
+ ENTRY_ADD,
+ ENTRY_DELETE,
+ ENTRY_MODIFY
+};
+
+nsAbLDAPProcessChangeLogData::nsAbLDAPProcessChangeLogData()
+: mUseChangeLog(false),
+ mChangeLogEntriesCount(0),
+ mEntriesAddedQueryCount(0)
+{
+ mRootDSEEntry.firstChangeNumber = 0;
+ mRootDSEEntry.lastChangeNumber = 0;
+}
+
+nsAbLDAPProcessChangeLogData::~nsAbLDAPProcessChangeLogData()
+{
+
+}
+
+NS_IMETHODIMP nsAbLDAPProcessChangeLogData::Init(nsIAbLDAPReplicationQuery * query, nsIWebProgressListener *progressListener)
+{
+ NS_ENSURE_ARG_POINTER(query);
+
+ // Here we are assuming that the caller will pass a nsAbLDAPChangeLogQuery object,
+ // an implementation derived from the implementation of nsIAbLDAPReplicationQuery.
+ nsresult rv = NS_OK;
+ mChangeLogQuery = do_QueryInterface(query, &rv);
+ if(NS_FAILED(rv))
+ return rv;
+
+ // Call the parent's Init now.
+ return nsAbLDAPProcessReplicationData::Init(query, progressListener);
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnLDAPBind(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t errCode;
+
+ nsresult rv = aMessage->GetErrorCode(&errCode);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+
+ if(errCode != nsILDAPErrors::SUCCESS) {
+ Done(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ switch(mState) {
+ case kAnonymousBinding :
+ rv = GetAuthData();
+ if(NS_SUCCEEDED(rv))
+ rv = mChangeLogQuery->QueryAuthDN(mAuthUserID);
+ if(NS_SUCCEEDED(rv))
+ mState = kSearchingAuthDN;
+ break;
+ case kAuthenticatedBinding :
+ rv = mChangeLogQuery->QueryRootDSE();
+ if(NS_SUCCEEDED(rv))
+ mState = kSearchingRootDSE;
+ break;
+ } //end of switch
+
+ if(NS_FAILED(rv))
+ Abort();
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnLDAPSearchEntry(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+
+ switch(mState)
+ {
+ case kSearchingAuthDN :
+ {
+ nsAutoCString authDN;
+ rv = aMessage->GetDn(authDN);
+ if(NS_SUCCEEDED(rv) && !authDN.IsEmpty())
+ mAuthDN = authDN.get();
+ }
+ break;
+ case kSearchingRootDSE:
+ rv = ParseRootDSEEntry(aMessage);
+ break;
+ case kFindingChanges:
+ rv = ParseChangeLogEntries(aMessage);
+ break;
+ // Fall through since we only add (for updates we delete and add)
+ case kReplicatingChanges:
+ case kReplicatingAll :
+ return nsAbLDAPProcessReplicationData::OnLDAPSearchEntry(aMessage);
+ }
+
+ if(NS_FAILED(rv))
+ Abort();
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnLDAPSearchResult(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t errorCode;
+
+ nsresult rv = aMessage->GetErrorCode(&errorCode);
+
+ if(NS_SUCCEEDED(rv))
+ {
+ if(errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED) {
+ switch(mState) {
+ case kSearchingAuthDN :
+ rv = OnSearchAuthDNDone();
+ break;
+ case kSearchingRootDSE:
+ {
+ // Before starting the changeLog check the DB file, if its not there or bogus
+ // we need to create a new one and set to all.
+ nsCOMPtr<nsIAddrBookSession> abSession = do_GetService(NS_ADDRBOOKSESSION_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ break;
+ nsCOMPtr<nsIFile> dbPath;
+ rv = abSession->GetUserProfileDirectory(getter_AddRefs(dbPath));
+ if (NS_FAILED(rv))
+ break;
+
+ nsAutoCString fileName;
+ rv = mDirectory->GetReplicationFileName(fileName);
+ if (NS_FAILED(rv))
+ break;
+
+ rv = dbPath->AppendNative(fileName);
+ if (NS_FAILED(rv))
+ break;
+
+ bool fileExists;
+ rv = dbPath->Exists(&fileExists);
+ if (NS_FAILED(rv))
+ break;
+
+ int64_t fileSize;
+ rv = dbPath->GetFileSize(&fileSize);
+ if(NS_FAILED(rv))
+ break;
+
+ if (!fileExists || !fileSize)
+ mUseChangeLog = false;
+
+ // Open / create the AB here since it calls Done,
+ // just return from here.
+ if (mUseChangeLog)
+ rv = OpenABForReplicatedDir(false);
+ else
+ rv = OpenABForReplicatedDir(true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Now start the appropriate query
+ rv = OnSearchRootDSEDone();
+ break;
+ }
+ case kFindingChanges:
+ rv = OnFindingChangesDone();
+ // If success we return from here since
+ // this changes state to kReplicatingChanges
+ // and it falls thru into the if clause below.
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ break;
+ case kReplicatingAll :
+ return nsAbLDAPProcessReplicationData::OnLDAPSearchResult(aMessage);
+ } // end of switch
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+ // If one of the changed entry in changelog is not found,
+ // continue with replicating the next one.
+ if(mState == kReplicatingChanges)
+ rv = OnReplicatingChangeDone();
+ } // end of outer if
+
+ if(NS_FAILED(rv))
+ Abort();
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::GetAuthData()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (!wwatch)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAuthPrompt> dialog;
+ nsresult rv = wwatch->GetNewAuthPrompter(0, getter_AddRefs(dialog));
+ if (NS_FAILED(rv))
+ return rv;
+ if (!dialog)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsILDAPURL> url;
+ rv = mQuery->GetReplicationURL(getter_AddRefs(url));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString serverUri;
+ rv = url->GetSpec(serverUri);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle));
+ if (NS_FAILED (rv))
+ return rv ;
+
+ nsString title;
+ rv = bundle->GetStringFromName(u"AuthDlgTitle", getter_Copies(title));
+ if (NS_FAILED (rv))
+ return rv ;
+
+ nsString desc;
+ rv = bundle->GetStringFromName(u"AuthDlgDesc", getter_Copies(desc));
+ if (NS_FAILED (rv))
+ return rv ;
+
+ nsString username;
+ nsString password;
+ bool btnResult = false;
+ rv = dialog->PromptUsernameAndPassword(title, desc,
+ NS_ConvertUTF8toUTF16(serverUri).get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ getter_Copies(username), getter_Copies(password),
+ &btnResult);
+ if(NS_SUCCEEDED(rv) && btnResult) {
+ CopyUTF16toUTF8(username, mAuthUserID);
+ CopyUTF16toUTF8(password, mAuthPswd);
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnSearchAuthDNDone()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsILDAPURL> url;
+ nsresult rv = mQuery->GetReplicationURL(getter_AddRefs(url));
+ if(NS_SUCCEEDED(rv))
+ rv = mQuery->ConnectToLDAPServer(url, mAuthDN);
+ if(NS_SUCCEEDED(rv)) {
+ mState = kAuthenticatedBinding;
+ rv = mDirectory->SetAuthDn(mAuthDN);
+ }
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::ParseRootDSEEntry(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Populate the RootDSEChangeLogEntry
+ CharPtrArrayGuard attrs;
+ nsresult rv = aMessage->GetAttributes(attrs.GetSizeAddr(), attrs.GetArrayAddr());
+ // No attributes
+ if(NS_FAILED(rv))
+ return rv;
+
+ for(int32_t i=attrs.GetSize()-1; i >= 0; i--) {
+ PRUnicharPtrArrayGuard vals;
+ rv = aMessage->GetValues(attrs.GetArray()[i], vals.GetSizeAddr(), vals.GetArrayAddr());
+ if(NS_FAILED(rv))
+ continue;
+ if(vals.GetSize()) {
+ if (!PL_strcasecmp(attrs[i], "changelog"))
+ CopyUTF16toUTF8(vals[0], mRootDSEEntry.changeLogDN);
+ if (!PL_strcasecmp(attrs[i], "firstChangeNumber"))
+ mRootDSEEntry.firstChangeNumber = atol(NS_LossyConvertUTF16toASCII(vals[0]).get());
+ if (!PL_strcasecmp(attrs[i], "lastChangeNumber"))
+ mRootDSEEntry.lastChangeNumber = atol(NS_LossyConvertUTF16toASCII(vals[0]).get());
+ if (!PL_strcasecmp(attrs[i], "dataVersion"))
+ CopyUTF16toUTF8(vals[0], mRootDSEEntry.dataVersion);
+ }
+ }
+
+ int32_t lastChangeNumber;
+ mDirectory->GetLastChangeNumber(&lastChangeNumber);
+
+ if ((mRootDSEEntry.lastChangeNumber > 0) &&
+ (lastChangeNumber < mRootDSEEntry.lastChangeNumber) &&
+ (lastChangeNumber > mRootDSEEntry.firstChangeNumber))
+ mUseChangeLog = true;
+
+ if (mRootDSEEntry.lastChangeNumber &&
+ (lastChangeNumber == mRootDSEEntry.lastChangeNumber)) {
+ Done(true); // We are up to date no need to replicate, db not open yet so call Done
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnSearchRootDSEDone()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+
+ if(mUseChangeLog) {
+ rv = mChangeLogQuery->QueryChangeLog(mRootDSEEntry.changeLogDN, mRootDSEEntry.lastChangeNumber);
+ if (NS_FAILED(rv))
+ return rv;
+ mState = kFindingChanges;
+ if(mListener)
+ mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, false);
+ }
+ else {
+ rv = mQuery->QueryAllEntries();
+ if (NS_FAILED(rv))
+ return rv;
+ mState = kReplicatingAll;
+ if(mListener)
+ mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, true);
+ }
+
+ rv = mDirectory->SetLastChangeNumber(mRootDSEEntry.lastChangeNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDirectory->SetDataVersion(mRootDSEEntry.dataVersion);
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::ParseChangeLogEntries(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Populate the RootDSEChangeLogEntry
+ CharPtrArrayGuard attrs;
+ nsresult rv = aMessage->GetAttributes(attrs.GetSizeAddr(), attrs.GetArrayAddr());
+ // No attributes
+ if(NS_FAILED(rv))
+ return rv;
+
+ nsAutoString targetDN;
+ UpdateOp operation = NO_OP;
+ for(int32_t i = attrs.GetSize()-1; i >= 0; i--) {
+ PRUnicharPtrArrayGuard vals;
+ rv = aMessage->GetValues(attrs.GetArray()[i], vals.GetSizeAddr(), vals.GetArrayAddr());
+ if(NS_FAILED(rv))
+ continue;
+ if(vals.GetSize()) {
+ if (!PL_strcasecmp(attrs[i], "targetdn"))
+ targetDN = vals[0];
+ if (!PL_strcasecmp(attrs[i], "changetype")) {
+ if (!Compare(nsDependentString(vals[0]), NS_LITERAL_STRING("add"), nsCaseInsensitiveStringComparator()))
+ operation = ENTRY_ADD;
+ if (!Compare(nsDependentString(vals[0]), NS_LITERAL_STRING("modify"), nsCaseInsensitiveStringComparator()))
+ operation = ENTRY_MODIFY;
+ if (!Compare(nsDependentString(vals[0]), NS_LITERAL_STRING("delete"), nsCaseInsensitiveStringComparator()))
+ operation = ENTRY_DELETE;
+ }
+ }
+ }
+
+ mChangeLogEntriesCount++;
+ if(!(mChangeLogEntriesCount % 10)) { // Inform the listener every 10 entries
+ mListener->OnProgressChange(nullptr,nullptr,mChangeLogEntriesCount, -1, mChangeLogEntriesCount, -1);
+ // In case if the LDAP Connection thread is starved and causes problem
+ // uncomment this one and try.
+ // PR_Sleep(PR_INTERVAL_NO_WAIT); // give others a chance
+ }
+
+#ifdef DEBUG_rdayal
+ printf ("ChangeLog Replication : Updated Entry : %s for OpType : %u\n",
+ NS_ConvertUTF16toUTF8(targetDN).get(), operation);
+#endif
+
+ switch(operation) {
+ case ENTRY_ADD:
+ // Add the DN to the add list if not already in the list
+ if(!(mEntriesToAdd.IndexOf(targetDN) >= 0))
+ mEntriesToAdd.AppendString(targetDN);
+ break;
+ case ENTRY_DELETE:
+ // Do not check the return here since delete may fail if
+ // entry deleted in changelog does not exist in DB
+ // for e.g if the user specifies a filter, so go next entry
+ DeleteCard(targetDN);
+ break;
+ case ENTRY_MODIFY:
+ // For modify, delete the entry from DB and add updated entry
+ // we do this since we cannot access the changes attribs of changelog
+ rv = DeleteCard(targetDN);
+ if (NS_SUCCEEDED(rv))
+ if(!(mEntriesToAdd.IndexOf(targetDN) >= 0))
+ mEntriesToAdd.AppendString(targetDN);
+ break;
+ default:
+ // Should not come here, would come here only
+ // if the entry is not a changeLog entry
+ NS_WARNING("nsAbLDAPProcessChangeLogData::ParseChangeLogEntries"
+ "Not an changelog entry");
+ }
+
+ // Go ahead processing the next entry, a modify or delete DB operation
+ // can 'correctly' fail if the entry is not present in the DB,
+ // e.g. in case a filter is specified.
+ return NS_OK;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnFindingChangesDone()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+#ifdef DEBUG_rdayal
+ printf ("ChangeLog Replication : Finding Changes Done \n");
+#endif
+
+ nsresult rv = NS_OK;
+
+ // No entries to add/update (for updates too we delete and add) entries,
+ // we took care of deletes in ParseChangeLogEntries, all Done!
+ mEntriesAddedQueryCount = mEntriesToAdd.Count();
+ if(mEntriesAddedQueryCount <= 0) {
+ if(mReplicationDB && mDBOpen) {
+ // Close the DB, no need to commit since we have not made
+ // any changes yet to the DB.
+ rv = mReplicationDB->Close(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close(no commit) on Success failed");
+ mDBOpen = false;
+ // Once are done with the replication file, delete the backup file
+ if(mBackupReplicationFile) {
+ rv = mBackupReplicationFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed");
+ }
+ }
+ Done(true);
+ return NS_OK;
+ }
+
+ // Decrement the count first to get the correct array element
+ mEntriesAddedQueryCount--;
+ rv = mChangeLogQuery->QueryChangedEntries(NS_ConvertUTF16toUTF8(*(mEntriesToAdd[mEntriesAddedQueryCount])));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if(mListener && NS_SUCCEEDED(rv))
+ mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, true);
+
+ mState = kReplicatingChanges;
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnReplicatingChangeDone()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+
+ if(!mEntriesAddedQueryCount)
+ {
+ if(mReplicationDB && mDBOpen) {
+ rv = mReplicationDB->Close(true); // Commit and close the DB
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close (commit) on Success failed");
+ mDBOpen = false;
+ }
+ // Once we done with the replication file, delete the backup file.
+ if(mBackupReplicationFile) {
+ rv = mBackupReplicationFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed");
+ }
+ Done(true); // All data is received
+ return NS_OK;
+ }
+
+ // Remove the entry already added from the list and query the next one.
+ if(mEntriesAddedQueryCount < mEntriesToAdd.Count() && mEntriesAddedQueryCount >= 0)
+ mEntriesToAdd.RemoveStringAt(mEntriesAddedQueryCount);
+ mEntriesAddedQueryCount--;
+ rv = mChangeLogQuery->QueryChangedEntries(NS_ConvertUTF16toUTF8(*(mEntriesToAdd[mEntriesAddedQueryCount])));
+
+ return rv;
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogData.h b/mailnews/addrbook/src/nsAbLDAPChangeLogData.h
new file mode 100644
index 000000000..9300e34ec
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPChangeLogData.h
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPChangeLogData_h__
+#define nsAbLDAPChangeLogData_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbLDAPReplicationData.h"
+#include "nsAbLDAPChangeLogQuery.h"
+
+typedef struct {
+ nsCString changeLogDN;
+ int32_t firstChangeNumber;
+ int32_t lastChangeNumber;
+ nsCString dataVersion;
+} RootDSEChangeLogEntry;
+
+class nsAbLDAPProcessChangeLogData : public nsAbLDAPProcessReplicationData
+{
+public :
+
+ nsAbLDAPProcessChangeLogData();
+
+ NS_IMETHOD Init(nsIAbLDAPReplicationQuery * query, nsIWebProgressListener *progressListener);
+
+protected :
+ ~nsAbLDAPProcessChangeLogData();
+
+ nsCOMPtr <nsIAbLDAPChangeLogQuery> mChangeLogQuery;
+
+ nsresult OnLDAPBind(nsILDAPMessage *aMessage);
+ nsresult OnLDAPSearchEntry(nsILDAPMessage *aMessage) override;
+ nsresult OnLDAPSearchResult(nsILDAPMessage *aMessage) override;
+
+ nsresult ParseChangeLogEntries(nsILDAPMessage *aMessage);
+ nsresult ParseRootDSEEntry(nsILDAPMessage *aMessage);
+
+ nsresult GetAuthData(); // displays username and password prompt
+ nsCString mAuthUserID; // user id of the user making the connection
+
+ nsresult OnSearchAuthDNDone();
+ nsresult OnSearchRootDSEDone();
+ nsresult OnFindingChangesDone();
+ nsresult OnReplicatingChangeDone();
+
+ RootDSEChangeLogEntry mRootDSEEntry;
+ bool mUseChangeLog;
+ int32_t mChangeLogEntriesCount;
+
+ int32_t mEntriesAddedQueryCount;
+ nsStringArray mEntriesToAdd;
+};
+
+
+#endif // nsAbLDAPChangeLogData_h__
+
diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp
new file mode 100644
index 000000000..1bdf474d6
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsCOMPtr.h"
+#include "nsAbLDAPChangeLogQuery.h"
+#include "nsAbLDAPReplicationService.h"
+#include "nsAbLDAPChangeLogData.h"
+#include "nsAbUtils.h"
+#include "prprf.h"
+#include "nsDirPrefs.h"
+#include "nsAbBaseCID.h"
+
+
+// The tables below were originally in nsAbLDAPProperties.cpp, which has since
+// gone away.
+static const char * sChangeLogRootDSEAttribs[] =
+{
+ "changelog",
+ "firstChangeNumber",
+ "lastChangeNumber",
+ "dataVersion"
+};
+static const char * sChangeLogEntryAttribs[] =
+{
+ "targetdn",
+ "changetype"
+};
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbLDAPChangeLogQuery, nsAbLDAPReplicationQuery, nsIAbLDAPChangeLogQuery)
+
+nsAbLDAPChangeLogQuery::nsAbLDAPChangeLogQuery()
+{
+}
+
+nsAbLDAPChangeLogQuery::~nsAbLDAPChangeLogQuery()
+{
+
+}
+
+// this is to be defined only till this is not hooked to SSL to get authDN and authPswd
+#define USE_AUTHDLG
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::Init(const nsACString & aPrefName, nsIWebProgressListener *aProgressListener)
+{
+ if(aPrefName.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ mDirPrefName = aPrefName;
+
+ nsresult rv = InitLDAPData();
+ if(NS_FAILED(rv))
+ return rv;
+
+ // create the ChangeLog Data Processor
+ mDataProcessor = do_CreateInstance(NS_ABLDAP_PROCESSCHANGELOGDATA_CONTRACTID, &rv);
+ if(NS_FAILED(rv))
+ return rv;
+
+ // 'this' initialized
+ mInitialized = true;
+
+ return mDataProcessor->Init(this, aProgressListener);
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::DoReplicationQuery()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+#ifdef USE_AUTHDLG
+ return ConnectToLDAPServer(mURL, EmptyCString());
+#else
+ mDataProcessor->PopulateAuthData();
+ return ConnectToLDAPServer(mURL, mAuthDN);
+#endif
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryAuthDN(const nsACString & aValueUsedToFindDn)
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ nsresult rv = mDirectory->GetAttributeMap(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString filter;
+ rv = attrMap->GetFirstAttribute(NS_LITERAL_CSTRING("PrimaryEmail"), filter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filter += '=';
+ filter += aValueUsedToFindDn;
+
+ nsAutoCString dn;
+ rv = mURL->GetDn(dn);
+ if(NS_FAILED(rv))
+ return rv;
+
+ rv = CreateNewLDAPOperation();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX We really should be using LDAP_NO_ATTRS here once its exposed via
+ // the XPCOM layer of the directory code.
+ return mOperation->SearchExt(dn, nsILDAPURL::SCOPE_SUBTREE, filter,
+ 0, nullptr,
+ 0, 0);
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryRootDSE()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = CreateNewLDAPOperation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mOperation->SearchExt(EmptyCString(), nsILDAPURL::SCOPE_BASE,
+ NS_LITERAL_CSTRING("objectclass=*"),
+ sizeof(sChangeLogRootDSEAttribs),
+ sChangeLogRootDSEAttribs, 0, 0);
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryChangeLog(const nsACString & aChangeLogDN, int32_t aLastChangeNo)
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (aChangeLogDN.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ int32_t lastChangeNumber;
+ nsresult rv = mDirectory->GetLastChangeNumber(&lastChangeNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make sure that the filter here just have one condition
+ // and should not be enclosed in enclosing brackets.
+ // also condition '>' doesnot work, it should be '>='/
+ nsAutoCString filter (NS_LITERAL_CSTRING("changenumber>="));
+ filter.AppendInt(lastChangeNumber + 1);
+
+ rv = CreateNewLDAPOperation();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mOperation->SearchExt(aChangeLogDN, nsILDAPURL::SCOPE_ONELEVEL, filter,
+ sizeof(sChangeLogEntryAttribs),
+ sChangeLogEntryAttribs, 0, 0);
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryChangedEntries(const nsACString & aChangedEntryDN)
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+ if(aChangedEntryDN.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ nsAutoCString urlFilter;
+ nsresult rv = mURL->GetFilter(urlFilter);
+ if(NS_FAILED(rv))
+ return rv;
+
+ int32_t scope;
+ rv = mURL->GetScope(&scope);
+ if(NS_FAILED(rv))
+ return rv;
+
+ CharPtrArrayGuard attributes;
+ rv = mURL->GetAttributes(attributes.GetSizeAddr(), attributes.GetArrayAddr());
+ if(NS_FAILED(rv))
+ return rv;
+
+ rv = CreateNewLDAPOperation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mOperation->SearchExt(aChangedEntryDN, scope, urlFilter,
+ attributes.GetSize(), attributes.GetArray(),
+ 0, 0);
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h
new file mode 100644
index 000000000..9652f470e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPChangeLogQuery_h__
+#define nsAbLDAPChangeLogQuery_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbLDAPReplicationQuery.h"
+#include "nsStringGlue.h"
+
+class nsAbLDAPChangeLogQuery : public nsIAbLDAPChangeLogQuery,
+ public nsAbLDAPReplicationQuery
+{
+public :
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABLDAPCHANGELOGQUERY
+
+ nsAbLDAPChangeLogQuery();
+ virtual ~nsAbLDAPChangeLogQuery();
+
+ NS_IMETHOD DoReplicationQuery() override;
+ NS_IMETHOD Init(const nsACString & aPrefName, nsIWebProgressListener *aProgressListener);
+};
+
+#endif // nsAbLDAPChangeLogQuery_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPDirFactory.cpp b/mailnews/addrbook/src/nsAbLDAPDirFactory.cpp
new file mode 100644
index 000000000..299f2a953
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirFactory.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPDirFactory.h"
+#include "nsAbUtils.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsIAbManager.h"
+#include "nsIAbDirectory.h"
+#include "nsAbLDAPDirectory.h"
+
+#include "nsEnumeratorUtils.h"
+#include "nsAbBaseCID.h"
+
+NS_IMPL_ISUPPORTS(nsAbLDAPDirFactory, nsIAbDirFactory)
+
+nsAbLDAPDirFactory::nsAbLDAPDirFactory()
+{
+}
+
+nsAbLDAPDirFactory::~nsAbLDAPDirFactory()
+{
+}
+
+NS_IMETHODIMP
+nsAbLDAPDirFactory::GetDirectories(const nsAString &aDirName,
+ const nsACString &aURI,
+ const nsACString &aPrefName,
+ nsISimpleEnumerator **aDirectories)
+{
+ NS_ENSURE_ARG_POINTER(aDirectories);
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ if (Substring(aURI, 0, 5).EqualsLiteral("ldap:") ||
+ Substring(aURI, 0, 6).EqualsLiteral("ldaps:")) {
+ /*
+ * If the URI starts with ldap: or ldaps:
+ * then this directory is an LDAP directory.
+ *
+ * We don't want to use the ldap:// or ldaps:// URI
+ * as the URI because the ldap:// or ldaps:// URI
+ * will contain the hostname, basedn, port, etc.
+ * so if those attributes changed, we'll run into the
+ * the same problem that we hit with changing username / hostname
+ * for mail servers. To solve this problem, we add an extra
+ * level of indirection. The URI that we generate
+ * (the bridge URI) will be moz-abldapdirectory://<prefName>
+ * and when we need the hostname, basedn, port, etc,
+ * we'll use the <prefName> to get the necessary prefs.
+ * note, <prefName> does not change.
+ */
+ nsAutoCString bridgeURI;
+ bridgeURI = NS_LITERAL_CSTRING(kLDAPDirectoryRoot);
+ bridgeURI += aPrefName;
+ rv = abManager->GetDirectory(bridgeURI, getter_AddRefs(directory));
+ }
+ else {
+ rv = abManager->GetDirectory(aURI, getter_AddRefs(directory));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewSingletonEnumerator(aDirectories, directory);
+}
+
+/* void deleteDirectory (in nsIAbDirectory directory); */
+NS_IMETHODIMP
+nsAbLDAPDirFactory::DeleteDirectory(nsIAbDirectory *directory)
+{
+ // No actual deletion - as the LDAP Address Book is not physically
+ // created in the corresponding CreateDirectory() unlike the Personal
+ // Address Books. But we still need to return NS_OK from here.
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPDirFactory.h b/mailnews/addrbook/src/nsAbLDAPDirFactory.h
new file mode 100644
index 000000000..9285fbc05
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirFactory.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPDirFactory_h__
+#define nsAbLDAPDirFactory_h__
+
+#include "nsIAbDirFactory.h"
+
+class nsAbLDAPDirFactory : public nsIAbDirFactory
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORY
+
+ nsAbLDAPDirFactory();
+
+private:
+ virtual ~nsAbLDAPDirFactory();
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectory.cpp b/mailnews/addrbook/src/nsAbLDAPDirectory.cpp
new file mode 100644
index 000000000..d1bb484c0
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectory.cpp
@@ -0,0 +1,948 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPDirectory.h"
+
+#include "nsAbQueryStringToExpression.h"
+
+#include "nsAbBaseCID.h"
+#include "nsIAbManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsIIOService.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsILDAPURL.h"
+#include "nsILDAPConnection.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsILDAPModification.h"
+#include "nsILDAPService.h"
+#include "nsIAbLDAPCard.h"
+#include "nsAbUtils.h"
+#include "nsArrayUtils.h"
+#include "nsIPrefService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+#define kDefaultMaxHits 100
+
+using namespace mozilla;
+
+nsAbLDAPDirectory::nsAbLDAPDirectory() :
+ nsAbDirProperty(),
+ mPerformingQuery(false),
+ mContext(0),
+ mLock("nsAbLDAPDirectory.mLock")
+{
+}
+
+nsAbLDAPDirectory::~nsAbLDAPDirectory()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbLDAPDirectory, nsAbDirProperty,
+ nsISupportsWeakReference, nsIAbDirSearchListener,
+ nsIAbLDAPDirectory)
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetPropertiesChromeURI(nsACString &aResult)
+{
+ aResult.AssignLiteral("chrome://messenger/content/addressbook/pref-directory-add.xul");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::Init(const char* aURI)
+{
+ // We need to ensure that the m_DirPrefId is initialized properly
+ nsAutoCString uri(aURI);
+
+ // Find the first ? (of the search params) if there is one.
+ // We know we can start at the end of the moz-abldapdirectory:// because
+ // that's the URI we should have been passed.
+ int32_t searchCharLocation = uri.FindChar('?', kLDAPDirectoryRootLen);
+
+ if (searchCharLocation == -1)
+ m_DirPrefId = Substring(uri, kLDAPDirectoryRootLen);
+ else
+ m_DirPrefId = Substring(uri, kLDAPDirectoryRootLen, searchCharLocation - kLDAPDirectoryRootLen);
+
+ return nsAbDirProperty::Init(aURI);
+}
+
+nsresult nsAbLDAPDirectory::Initiate()
+{
+ return NS_OK;
+}
+
+/*
+ *
+ * nsIAbDirectory methods
+ *
+ */
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetChildNodes(nsISimpleEnumerator* *aResult)
+{
+ return NS_NewEmptyEnumerator(aResult);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetChildCards(nsISimpleEnumerator** result)
+{
+ nsresult rv;
+
+ // when offline, we need to get the child cards for the local, replicated mdb directory
+ bool offline;
+ nsCOMPtr <nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ rv = ioService->GetOffline(&offline);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (offline) {
+ nsCString fileName;
+ rv = GetReplicationFileName(fileName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if there is no fileName, bail out now.
+ if (fileName.IsEmpty())
+ return NS_OK;
+
+ // perform the same query, but on the local directory
+ nsAutoCString localDirectoryURI(NS_LITERAL_CSTRING(kMDBDirectoryRoot));
+ localDirectoryURI.Append(fileName);
+ if (mIsQueryURI)
+ {
+ localDirectoryURI.AppendLiteral("?");
+ localDirectoryURI.Append(mQueryString);
+ }
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(localDirectoryURI,
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->GetChildCards(result);
+ }
+ else {
+ // Start the search
+ rv = StartSearch();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewEmptyEnumerator(result);
+ }
+
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mIsQueryURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::HasCard(nsIAbCard* card, bool* hasCard)
+{
+ nsresult rv = Initiate ();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enter lock
+ MutexAutoLock lock (mLock);
+
+ *hasCard = mCache.Get(card, nullptr);
+ if (!*hasCard && mPerformingQuery)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetLDAPURL(nsILDAPURL** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // Rather than using GetURI here we call GetStringValue directly so
+ // we can handle the case where the URI isn't specified (see comments
+ // below)
+ nsAutoCString URI;
+ nsresult rv = GetStringValue("uri", EmptyCString(), URI);
+ if (NS_FAILED(rv) || URI.IsEmpty())
+ {
+ /*
+ * A recent change in Mozilla now means that the LDAP Address Book
+ * URI is based on the unique preference name value i.e.
+ * [moz-abldapdirectory://prefName]
+ * Prior to this valid change it was based on the actual uri i.e.
+ * [moz-abldapdirectory://host:port/basedn]
+ * Basing the resource on the prefName allows these attributes to
+ * change.
+ *
+ * But the uri value was also the means by which third-party
+ * products could integrate with Mozilla's LDAP Address Books
+ * without necessarily having an entry in the preferences file
+ * or more importantly needing to be able to change the
+ * preferences entries. Thus to set the URI Spec now, it is
+ * only necessary to read the uri pref entry, while in the
+ * case where it is not a preference, we need to replace the
+ * "moz-abldapdirectory".
+ */
+ URI = mURINoQuery;
+ if (StringBeginsWith(URI, NS_LITERAL_CSTRING(kLDAPDirectoryRoot)))
+ URI.Replace(0, kLDAPDirectoryRootLen, NS_LITERAL_CSTRING("ldap://"));
+ }
+
+ nsCOMPtr<nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIURI> result;
+ rv = ioService->NewURI(URI, nullptr, nullptr, getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(result, aResult);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetLDAPURL(nsILDAPURL *aUrl)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+
+ nsAutoCString oldUrl;
+ // Note, it doesn't matter if GetStringValue fails - we'll just send an
+ // update if its blank (i.e. old value not set).
+ GetStringValue("uri", EmptyCString(), oldUrl);
+
+ // Actually set the new value.
+ nsCString tempLDAPURL;
+ nsresult rv = aUrl->GetSpec(tempLDAPURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetStringValue("uri", tempLDAPURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now we need to send an update which will ensure our indicators and
+ // listeners get updated correctly.
+
+ // See if they both start with ldaps: or ldap:
+ bool newIsNotSecure = StringHead(tempLDAPURL, 5).Equals("ldap:");
+
+ if (oldUrl.IsEmpty() ||
+ StringHead(oldUrl, 5).Equals("ldap:") != newIsNotSecure)
+ {
+ // They don't so its time to send round an update.
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We inherit from nsIAbDirectory, so this static cast should be safe.
+ abManager->NotifyItemPropertyChanged(static_cast<nsIAbDirectory*>(this),
+ "IsSecure",
+ (newIsNotSecure ? u"true" : u"false"),
+ (newIsNotSecure ? u"false" : u"true"));
+ }
+
+ return NS_OK;
+}
+
+/*
+ *
+ * nsIAbDirectorySearch methods
+ *
+ */
+
+NS_IMETHODIMP nsAbLDAPDirectory::StartSearch ()
+{
+ if (!mIsQueryURI || mQueryString.IsEmpty())
+ return NS_OK;
+
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = StopSearch();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression;
+ rv = nsAbQueryStringToExpression::Convert(mQueryString,
+ getter_AddRefs(expression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetExpression(expression);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetQuerySubDirectories(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the max hits to return
+ int32_t maxHits;
+ rv = GetMaxHits(&maxHits);
+ if (NS_FAILED(rv))
+ maxHits = kDefaultMaxHits;
+
+ // get the appropriate ldap attribute map, and pass it in via the
+ // TypeSpecificArgument
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ rv = GetAttributeMap(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> typeSpecificArg = do_QueryInterface(attrMap, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetTypeSpecificArg(attrMap);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mDirectoryQuery)
+ {
+ mDirectoryQuery = do_CreateInstance(NS_ABLDAPDIRECTORYQUERY_CONTRACTID,
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Perform the query
+ rv = mDirectoryQuery->DoQuery(this, arguments, this, maxHits, 0, &mContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enter lock
+ MutexAutoLock lock(mLock);
+ mPerformingQuery = true;
+ mCache.Clear();
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::StopSearch ()
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enter lock
+ {
+ MutexAutoLock lockGuard(mLock);
+ if (!mPerformingQuery)
+ return NS_OK;
+ mPerformingQuery = false;
+ }
+ // Exit lock
+
+ if (!mDirectoryQuery)
+ return NS_ERROR_NULL_POINTER;
+
+ return mDirectoryQuery->StopQuery(mContext);
+}
+
+/*
+ *
+ * nsAbDirSearchListenerContext methods
+ *
+ */
+NS_IMETHODIMP nsAbLDAPDirectory::OnSearchFinished(int32_t aResult, const nsAString &aErrorMessage)
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+ mPerformingQuery = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::OnSearchFoundCard(nsIAbCard* card)
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enter lock
+ {
+ MutexAutoLock lock(mLock);
+ mCache.Put(card, card);
+ }
+ // Exit lock
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if(NS_SUCCEEDED(rv))
+ abManager->NotifyDirectoryItemAdded(this, card);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetSupportsMailingLists(bool *aSupportsMailingsLists)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsMailingsLists);
+ *aSupportsMailingsLists = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetReadOnly(bool *aReadOnly)
+{
+ NS_ENSURE_ARG_POINTER(aReadOnly);
+
+ *aReadOnly = true;
+
+#ifdef MOZ_EXPERIMENTAL_WRITEABLE_LDAP
+ bool readOnly;
+ nsresult rv = GetBoolValue("readonly", false, &readOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (readOnly)
+ return NS_OK;
+
+ // when online, we'll allow writing as well
+ bool offline;
+ nsCOMPtr <nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+
+ rv = ioService->GetOffline(&offline);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!offline)
+ *aReadOnly = false;
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetIsRemote(bool *aIsRemote)
+{
+ NS_ENSURE_ARG_POINTER(aIsRemote);
+ *aIsRemote = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetIsSecure(bool *aIsSecure)
+{
+ NS_ENSURE_ARG_POINTER(aIsSecure);
+
+ nsAutoCString URI;
+ nsresult rv = GetStringValue("uri", EmptyCString(), URI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // to determine if this is a secure directory, check if the uri is ldaps:// or not
+ *aIsSecure = (strncmp(URI.get(), "ldaps:", 6) == 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::UseForAutocomplete(const nsACString &aIdentityKey,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // Set this to false by default to make the code easier below.
+ *aResult = false;
+
+ nsresult rv;
+ bool offline = false;
+ nsCOMPtr <nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+
+ rv = ioService->GetOffline(&offline);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we're online, then don't allow search during local autocomplete - must
+ // use the separate LDAP autocomplete session due to the current interfaces
+ if (!offline)
+ return NS_OK;
+
+ // Is the use directory pref set for autocompletion?
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool useDirectory = false;
+ rv = prefs->GetBoolPref("ldap_2.autoComplete.useDirectory", &useDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // No need to search if not set up globally for LDAP autocompletion and we've
+ // not been given an identity.
+ if (!useDirectory && aIdentityKey.IsEmpty())
+ return NS_OK;
+
+ nsCString prefName;
+ if (!aIdentityKey.IsEmpty())
+ {
+ // If we have an identity string, try and find out the required directory
+ // server.
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+
+ // If we failed, just return, we can't do much about this.
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = accountManager->GetIdentity(aIdentityKey, getter_AddRefs(identity));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool overrideGlobalPref = false;
+ identity->GetOverrideGlobalPref(&overrideGlobalPref);
+ if (overrideGlobalPref)
+ identity->GetDirectoryServer(prefName);
+ }
+ }
+
+ // If the preference name is still empty but useDirectory is false, then
+ // the global one is not available, nor is the overriden one.
+ if (prefName.IsEmpty() && !useDirectory)
+ return NS_OK;
+ }
+
+ // If we failed to get the identity preference, or the pref name is empty
+ // try the global preference.
+ if (prefName.IsEmpty())
+ {
+ nsresult rv = prefs->GetCharPref("ldap_2.autoComplete.directoryServer",
+ getter_Copies(prefName));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // Now see if the pref name matches our pref id.
+ if (prefName.Equals(m_DirPrefId))
+ {
+ // Yes it does, one last check - does the replication file exist?
+ nsresult rv;
+ nsCOMPtr<nsIFile> databaseFile;
+ // If we can't get the file, then there is no database to use
+ if (NS_FAILED(GetReplicationFile(getter_AddRefs(databaseFile))))
+ return NS_OK;
+
+ bool exists;
+ rv = databaseFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = exists;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetSearchClientControls(nsIMutableArray **aControls)
+{
+ NS_IF_ADDREF(*aControls = mSearchClientControls);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetSearchClientControls(nsIMutableArray *aControls)
+{
+ mSearchClientControls = aControls;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetSearchServerControls(nsIMutableArray **aControls)
+{
+ NS_IF_ADDREF(*aControls = mSearchServerControls);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetSearchServerControls(nsIMutableArray *aControls)
+{
+ mSearchServerControls = aControls;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetProtocolVersion(uint32_t *aProtocolVersion)
+{
+ nsAutoCString versionString;
+
+ nsresult rv = GetStringValue("protocolVersion", NS_LITERAL_CSTRING("3"), versionString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aProtocolVersion = versionString.EqualsLiteral("3") ?
+ (uint32_t)nsILDAPConnection::VERSION3 :
+ (uint32_t)nsILDAPConnection::VERSION2;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetProtocolVersion(uint32_t aProtocolVersion)
+{
+ // XXX We should cancel any existing LDAP connections here and
+ // be ready to re-initialise them with the new auth details.
+ return SetStringValue("protocolVersion",
+ aProtocolVersion == nsILDAPConnection::VERSION3 ?
+ NS_LITERAL_CSTRING("3") : NS_LITERAL_CSTRING("2"));
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetMaxHits(int32_t *aMaxHits)
+{
+ return GetIntValue("maxHits", kDefaultMaxHits, aMaxHits);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetMaxHits(int32_t aMaxHits)
+{
+ return SetIntValue("maxHits", aMaxHits);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetReplicationFileName(nsACString &aReplicationFileName)
+{
+ return GetStringValue("filename", EmptyCString(), aReplicationFileName);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetReplicationFileName(const nsACString &aReplicationFileName)
+{
+ return SetStringValue("filename", aReplicationFileName);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetAuthDn(nsACString &aAuthDn)
+{
+ return GetStringValue("auth.dn", EmptyCString(), aAuthDn);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetAuthDn(const nsACString &aAuthDn)
+{
+ // XXX We should cancel any existing LDAP connections here and
+ // be ready to re-initialise them with the new auth details.
+ return SetStringValue("auth.dn", aAuthDn);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetSaslMechanism(nsACString &aSaslMechanism)
+{
+ return GetStringValue("auth.saslmech", EmptyCString(), aSaslMechanism);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetSaslMechanism(const nsACString &aSaslMechanism)
+{
+ return SetStringValue("auth.saslmech", aSaslMechanism);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetLastChangeNumber(int32_t *aLastChangeNumber)
+{
+ return GetIntValue("lastChangeNumber", -1, aLastChangeNumber);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetLastChangeNumber(int32_t aLastChangeNumber)
+{
+ return SetIntValue("lastChangeNumber", aLastChangeNumber);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetDataVersion(nsACString &aDataVersion)
+{
+ return GetStringValue("dataVersion", EmptyCString(), aDataVersion);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetDataVersion(const nsACString &aDataVersion)
+{
+ return SetStringValue("dataVersion", aDataVersion);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetAttributeMap(nsIAbLDAPAttributeMap **aAttributeMap)
+{
+ NS_ENSURE_ARG_POINTER(aAttributeMap);
+
+ nsresult rv;
+ nsCOMPtr<nsIAbLDAPAttributeMapService> mapSvc =
+ do_GetService("@mozilla.org/addressbook/ldap-attribute-map-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mapSvc->GetMapForPrefBranch(m_DirPrefId, aAttributeMap);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetReplicationFile(nsIFile **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCString fileName;
+ nsresult rv = GetStringValue("filename", EmptyCString(), fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fileName.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIFile> profileDir;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = profileDir->AppendNative(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = profileDir);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetReplicationDatabase(nsIAddrDatabase **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> databaseFile;
+ rv = GetReplicationFile(getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory =
+ do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return addrDBFactory->Open(databaseFile, false /* no create */, true,
+ aResult);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::AddCard(nsIAbCard *aUpdatedCard,
+ nsIAbCard **aAddedCard)
+{
+ NS_ENSURE_ARG_POINTER(aUpdatedCard);
+ NS_ENSURE_ARG_POINTER(aAddedCard);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ nsresult rv = GetAttributeMap(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a new LDAP card
+ nsCOMPtr<nsIAbLDAPCard> card =
+ do_CreateInstance(NS_ABLDAPCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy over the card data
+ nsCOMPtr<nsIAbCard> copyToCard = do_QueryInterface(card, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copyToCard->Copy(aUpdatedCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Retrieve preferences
+ nsAutoCString prefString;
+ rv = GetRdnAttributes(prefString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CharPtrArrayGuard rdnAttrs;
+ rv = SplitStringList(prefString, rdnAttrs.GetSizeAddr(),
+ rdnAttrs.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetObjectClasses(prefString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CharPtrArrayGuard objClass;
+ rv = SplitStringList(prefString, objClass.GetSizeAddr(),
+ objClass.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Process updates
+ nsCOMPtr<nsIArray> modArray;
+ rv = card->GetLDAPMessageInfo(attrMap, objClass.GetSize(), objClass.GetArray(),
+ nsILDAPModification::MOD_ADD, getter_AddRefs(modArray));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // For new cards, the base DN is the search base DN
+ nsCOMPtr<nsILDAPURL> currentUrl;
+ rv = GetLDAPURL(getter_AddRefs(currentUrl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString baseDN;
+ rv = currentUrl->GetDn(baseDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Calculate DN
+ nsAutoCString cardDN;
+ rv = card->BuildRdn(attrMap, rdnAttrs.GetSize(), rdnAttrs.GetArray(),
+ cardDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+ cardDN.AppendLiteral(",");
+ cardDN.Append(baseDN);
+
+ rv = card->SetDn(cardDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+ copyToCard->SetDirectoryId(ourUuid);
+
+ // Launch query
+ rv = DoModify(this, nsILDAPModification::MOD_ADD, cardDN, modArray,
+ EmptyCString(), EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aAddedCard = copyToCard);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::DeleteCards(nsIArray *aCards)
+{
+ uint32_t cardCount;
+ uint32_t i;
+ nsAutoCString cardDN;
+
+ nsresult rv = aCards->GetLength(&cardCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < cardCount; ++i)
+ {
+ nsCOMPtr<nsIAbLDAPCard> card(do_QueryElementAt(aCards, i, &rv));
+ if (NS_FAILED(rv))
+ {
+ NS_WARNING("Wrong type of card passed to nsAbLDAPDirectory::DeleteCards");
+ break;
+ }
+
+ // Set up the search ldap url - this is mURL
+ rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = card->GetDn(cardDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> realCard(do_QueryInterface(card));
+ realCard->SetDirectoryId(EmptyCString());
+
+ // Launch query
+ rv = DoModify(this, nsILDAPModification::MOD_DELETE, cardDN, nullptr,
+ EmptyCString(), EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::ModifyCard(nsIAbCard *aUpdatedCard)
+{
+ NS_ENSURE_ARG_POINTER(aUpdatedCard);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ nsresult rv = GetAttributeMap(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the LDAP card
+ nsCOMPtr<nsIAbLDAPCard> card = do_QueryInterface(aUpdatedCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Retrieve preferences
+ nsAutoCString prefString;
+ rv = GetObjectClasses(prefString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CharPtrArrayGuard objClass;
+ rv = SplitStringList(prefString, objClass.GetSizeAddr(),
+ objClass.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Process updates
+ nsCOMPtr<nsIArray> modArray;
+ rv = card->GetLDAPMessageInfo(attrMap, objClass.GetSize(), objClass.GetArray(),
+ nsILDAPModification::MOD_REPLACE, getter_AddRefs(modArray));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get current DN
+ nsAutoCString oldDN;
+ rv = card->GetDn(oldDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILDAPService> ldapSvc = do_GetService(
+ "@mozilla.org/network/ldap-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Retrieve base DN and RDN attributes
+ nsAutoCString baseDN;
+ nsAutoCString oldRDN;
+ CharPtrArrayGuard rdnAttrs;
+ rv = ldapSvc->ParseDn(oldDN.get(), oldRDN, baseDN,
+ rdnAttrs.GetSizeAddr(), rdnAttrs.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Calculate new RDN and check whether it has changed
+ nsAutoCString newRDN;
+ rv = card->BuildRdn(attrMap, rdnAttrs.GetSize(), rdnAttrs.GetArray(),
+ newRDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (newRDN.Equals(oldRDN))
+ {
+ // Launch query
+ rv = DoModify(this, nsILDAPModification::MOD_REPLACE, oldDN, modArray,
+ EmptyCString(), EmptyCString());
+ }
+ else
+ {
+ // Build and store the new DN
+ nsAutoCString newDN(newRDN);
+ newDN.AppendLiteral(",");
+ newDN.Append(baseDN);
+
+ rv = card->SetDn(newDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Launch query
+ rv = DoModify(this, nsILDAPModification::MOD_REPLACE, oldDN, modArray,
+ newRDN, baseDN);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetRdnAttributes(nsACString &aRdnAttributes)
+{
+ return GetStringValue("rdnAttributes", NS_LITERAL_CSTRING("cn"),
+ aRdnAttributes);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetRdnAttributes(const nsACString &aRdnAttributes)
+{
+ return SetStringValue("rdnAttributes", aRdnAttributes);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetObjectClasses(nsACString &aObjectClasses)
+{
+ return GetStringValue("objectClasses", NS_LITERAL_CSTRING(
+ "top,person,organizationalPerson,inetOrgPerson,mozillaAbPersonAlpha"),
+ aObjectClasses);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetObjectClasses(const nsACString &aObjectClasses)
+{
+ return SetStringValue("objectClasses", aObjectClasses);
+}
+
+nsresult nsAbLDAPDirectory::SplitStringList(
+ const nsACString& aString,
+ uint32_t *aCount,
+ char ***aValues)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ NS_ENSURE_ARG_POINTER(aValues);
+
+ nsTArray<nsCString> strarr;
+ ParseString(aString, ',', strarr);
+
+ char **cArray = nullptr;
+ if (!(cArray = static_cast<char **>(moz_xmalloc(
+ strarr.Length() * sizeof(char *)))))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ for (uint32_t i = 0; i < strarr.Length(); ++i)
+ {
+ if (!(cArray[i] = ToNewCString(strarr[i])))
+ {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(strarr.Length(), cArray);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ *aCount = strarr.Length();
+ *aValues = cArray;
+ return NS_OK;
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectory.h b/mailnews/addrbook/src/nsAbLDAPDirectory.h
new file mode 100644
index 000000000..6e5279a97
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectory.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPDirectory_h__
+#define nsAbLDAPDirectory_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbDirProperty.h"
+#include "nsAbLDAPDirectoryModify.h"
+#include "nsIAbDirectoryQuery.h"
+#include "nsIAbDirectorySearch.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsIMutableArray.h"
+#include "nsInterfaceHashtable.h"
+#include "mozilla/Mutex.h"
+
+class nsAbLDAPDirectory :
+ public nsAbDirProperty, // nsIAbDirectory
+ public nsAbLDAPDirectoryModify,
+ public nsIAbDirectorySearch,
+ public nsIAbLDAPDirectory,
+ public nsIAbDirSearchListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsAbLDAPDirectory();
+
+ NS_IMETHOD Init(const char *aUri) override;
+
+ // nsIAbDirectory methods
+ NS_IMETHOD GetPropertiesChromeURI(nsACString &aResult) override;
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD GetChildCards(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD GetIsQuery(bool *aResult) override;
+ NS_IMETHOD HasCard(nsIAbCard *cards, bool *hasCard) override;
+ NS_IMETHOD GetSupportsMailingLists(bool *aSupportsMailingsLists) override;
+ NS_IMETHOD GetReadOnly(bool *aReadOnly) override;
+ NS_IMETHOD GetIsRemote(bool *aIsRemote) override;
+ NS_IMETHOD GetIsSecure(bool *aIsRemote) override;
+ NS_IMETHOD UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) override;
+ NS_IMETHOD AddCard(nsIAbCard *aChildCard, nsIAbCard **aAddedCard) override;
+ NS_IMETHOD ModifyCard(nsIAbCard *aModifiedCard) override;
+ NS_IMETHOD DeleteCards(nsIArray *aCards) override;
+
+ // nsIAbDirectorySearch methods
+ NS_DECL_NSIABDIRECTORYSEARCH
+ NS_DECL_NSIABLDAPDIRECTORY
+ NS_DECL_NSIABDIRSEARCHLISTENER
+
+protected:
+ virtual ~nsAbLDAPDirectory();
+ nsresult Initiate();
+
+ nsresult SplitStringList(const nsACString& aString,
+ uint32_t *aCount,
+ char ***aValues);
+
+ bool mPerformingQuery;
+ int32_t mContext;
+ int32_t mMaxHits;
+
+ nsInterfaceHashtable<nsISupportsHashKey, nsIAbCard> mCache;
+
+ mozilla::Mutex mLock;
+ nsCOMPtr<nsIAbDirectoryQuery> mDirectoryQuery;
+ nsCOMPtr<nsIMutableArray> mSearchServerControls;
+ nsCOMPtr<nsIMutableArray> mSearchClientControls;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp
new file mode 100644
index 000000000..95af79c04
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp
@@ -0,0 +1,372 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPDirectoryModify.h"
+#include "nsILDAPMessage.h"
+#include "nsILDAPConnection.h"
+#include "nsILDAPErrors.h"
+#include "nsILDAPModification.h"
+#include "nsIServiceManager.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsIMutableArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include <stdio.h>
+
+using namespace mozilla;
+
+class nsAbModifyLDAPMessageListener : public nsAbLDAPListenerBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAbModifyLDAPMessageListener(const int32_t type,
+ const nsACString &cardDN,
+ nsIArray* modArray,
+ const nsACString &newRDN,
+ const nsACString &newBaseDN,
+ nsILDAPURL* directoryUrl,
+ nsILDAPConnection* connection,
+ nsIMutableArray* serverSearchControls,
+ nsIMutableArray* clientSearchControls,
+ const nsACString &login,
+ const int32_t timeOut = 0);
+ // nsILDAPMessageListener
+ NS_IMETHOD OnLDAPMessage(nsILDAPMessage *aMessage) override;
+
+protected:
+ virtual ~nsAbModifyLDAPMessageListener();
+
+ nsresult Cancel();
+ virtual void InitFailed(bool aCancelled = false) override;
+ virtual nsresult DoTask() override;
+ nsresult DoMainTask();
+ nsresult OnLDAPMessageModifyResult(nsILDAPMessage *aMessage);
+ nsresult OnLDAPMessageRenameResult(nsILDAPMessage *aMessage);
+
+ int32_t mType;
+ nsCString mCardDN;
+ nsCOMPtr<nsIArray> mModification;
+ nsCString mNewRDN;
+ nsCString mNewBaseDN;
+
+ bool mFinished;
+ bool mCanceled;
+ bool mFlagRename;
+
+ nsCOMPtr<nsILDAPOperation> mModifyOperation;
+ nsCOMPtr<nsIMutableArray> mServerSearchControls;
+ nsCOMPtr<nsIMutableArray> mClientSearchControls;
+};
+
+
+NS_IMPL_ISUPPORTS(nsAbModifyLDAPMessageListener, nsILDAPMessageListener)
+
+nsAbModifyLDAPMessageListener::nsAbModifyLDAPMessageListener(
+ const int32_t type,
+ const nsACString &cardDN,
+ nsIArray* modArray,
+ const nsACString &newRDN,
+ const nsACString &newBaseDN,
+ nsILDAPURL* directoryUrl,
+ nsILDAPConnection* connection,
+ nsIMutableArray* serverSearchControls,
+ nsIMutableArray* clientSearchControls,
+ const nsACString &login,
+ const int32_t timeOut) :
+ nsAbLDAPListenerBase(directoryUrl, connection, login, timeOut),
+ mType(type),
+ mCardDN(cardDN),
+ mModification(modArray),
+ mNewRDN(newRDN),
+ mNewBaseDN(newBaseDN),
+ mFinished(false),
+ mCanceled(false),
+ mFlagRename(false),
+ mServerSearchControls(serverSearchControls),
+ mClientSearchControls(clientSearchControls)
+{
+ if (mType == nsILDAPModification::MOD_REPLACE &&
+ !mNewRDN.IsEmpty() && !mNewBaseDN.IsEmpty())
+ mFlagRename = true;
+}
+
+nsAbModifyLDAPMessageListener::~nsAbModifyLDAPMessageListener ()
+{
+}
+
+nsresult nsAbModifyLDAPMessageListener::Cancel ()
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ if (mFinished || mCanceled)
+ return NS_OK;
+
+ mCanceled = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbModifyLDAPMessageListener::OnLDAPMessage(nsILDAPMessage *aMessage)
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t messageType;
+ rv = aMessage->GetType(&messageType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool cancelOperation = false;
+
+ // Enter lock
+ {
+ MutexAutoLock lock (mLock);
+
+ if (mFinished)
+ return NS_OK;
+
+ // for these messages, no matter the outcome, we're done
+ if ((messageType == nsILDAPMessage::RES_ADD) ||
+ (messageType == nsILDAPMessage::RES_DELETE) ||
+ (messageType == nsILDAPMessage::RES_MODIFY))
+ mFinished = true;
+ else if (mCanceled)
+ {
+ mFinished = true;
+ cancelOperation = true;
+ }
+ }
+ // Leave lock
+
+ // nsCOMPtr<nsIAbDirectoryQueryResult> queryResult;
+ if (!cancelOperation)
+ {
+ switch (messageType)
+ {
+ case nsILDAPMessage::RES_BIND:
+ rv = OnLDAPMessageBind(aMessage);
+ if (NS_FAILED(rv))
+ // We know the bind failed and hence the message has an error, so we
+ // can just call ModifyResult with the message and that'll sort it out
+ // for us.
+ rv = OnLDAPMessageModifyResult(aMessage);
+ break;
+ case nsILDAPMessage::RES_ADD:
+ case nsILDAPMessage::RES_MODIFY:
+ case nsILDAPMessage::RES_DELETE:
+ rv = OnLDAPMessageModifyResult(aMessage);
+ break;
+ case nsILDAPMessage::RES_MODDN:
+ mFlagRename = false;
+ rv = OnLDAPMessageRenameResult(aMessage);
+ if (NS_FAILED(rv))
+ // Rename failed, so we stop here
+ mFinished = true;
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ if (mModifyOperation)
+ rv = mModifyOperation->AbandonExt();
+
+ // reset because we might re-use this listener...except don't do this
+ // until the search is done, so we'll ignore results from a previous
+ // search.
+ mCanceled = mFinished = false;
+ }
+
+ return rv;
+}
+
+void nsAbModifyLDAPMessageListener::InitFailed(bool aCancelled)
+{
+ // XXX Just cancel the operation for now
+ // we'll need to review this when we've got the proper listeners in place.
+ Cancel();
+}
+
+nsresult nsAbModifyLDAPMessageListener::DoTask()
+{
+ nsresult rv;
+ mCanceled = mFinished = false;
+
+ mModifyOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mModifyOperation->Init (mConnection, this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX do we need the search controls?
+ rv = mModifyOperation->SetServerControls(mServerSearchControls);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mModifyOperation->SetClientControls(mClientSearchControls);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFlagRename)
+ return mModifyOperation->Rename(mCardDN, mNewRDN, mNewBaseDN, true);
+
+ switch (mType)
+ {
+ case nsILDAPModification::MOD_ADD:
+ return mModifyOperation->AddExt(mCardDN, mModification);
+ case nsILDAPModification::MOD_DELETE:
+ return mModifyOperation->DeleteExt(mCardDN);
+ case nsILDAPModification::MOD_REPLACE:
+ return mModifyOperation->ModifyExt(mCardDN, mModification);
+ default:
+ NS_ERROR("Bad LDAP modification requested");
+ return NS_ERROR_UNEXPECTED;
+ }
+}
+
+nsresult nsAbModifyLDAPMessageListener::OnLDAPMessageModifyResult(nsILDAPMessage *aMessage)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ int32_t errCode;
+ rv = aMessage->GetErrorCode(&errCode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (errCode != nsILDAPErrors::SUCCESS)
+ {
+ nsAutoCString errMessage;
+ rv = aMessage->GetErrorMessage(errMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ printf("LDAP modification failed (code: %i, message: %s)\n",
+ errCode, errMessage.get());
+ return NS_ERROR_FAILURE;
+ }
+
+ printf("LDAP modification succeeded\n");
+ return NS_OK;
+}
+
+nsresult nsAbModifyLDAPMessageListener::OnLDAPMessageRenameResult(nsILDAPMessage *aMessage)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ int32_t errCode;
+ rv = aMessage->GetErrorCode(&errCode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (errCode != nsILDAPErrors::SUCCESS)
+ {
+ nsAutoCString errMessage;
+ rv = aMessage->GetErrorMessage(errMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ printf("LDAP rename failed (code: %i, message: %s)\n",
+ errCode, errMessage.get());
+ return NS_ERROR_FAILURE;
+ }
+
+ // Rename succeeded, now update the card DN and
+ // process the main task
+ mCardDN.Assign(mNewRDN);
+ mCardDN.AppendLiteral(",");
+ mCardDN.Append(mNewBaseDN);
+
+ printf("LDAP rename succeeded\n");
+ return DoTask();
+}
+
+nsAbLDAPDirectoryModify::nsAbLDAPDirectoryModify()
+{
+}
+
+nsAbLDAPDirectoryModify::~nsAbLDAPDirectoryModify()
+{
+}
+
+nsresult nsAbLDAPDirectoryModify::DoModify(nsIAbLDAPDirectory *directory,
+ const int32_t &updateType,
+ const nsACString &cardDN,
+ nsIArray* modArray,
+ const nsACString &newRDN,
+ const nsACString &newBaseDN)
+{
+ NS_ENSURE_ARG_POINTER(directory);
+ // modArray may be null in the delete operation case.
+ if (!modArray &&
+ (updateType == nsILDAPModification::MOD_ADD ||
+ updateType == nsILDAPModification::MOD_REPLACE))
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ // it's an error if we don't have a dn
+ if (cardDN.IsEmpty())
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsILDAPURL> currentUrl;
+ rv = directory->GetLDAPURL(getter_AddRefs(currentUrl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the ldap connection
+ nsCOMPtr<nsILDAPConnection> ldapConnection =
+ do_CreateInstance(NS_LDAPCONNECTION_CONTRACTID, &rv);
+
+ nsCOMPtr<nsIMutableArray> serverSearchControls;
+ rv = directory->GetSearchServerControls(getter_AddRefs(serverSearchControls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> clientSearchControls;
+ rv = directory->GetSearchClientControls(getter_AddRefs(clientSearchControls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /*
+ // XXX we need to fix how this all works - specifically, see the first patch
+ // on bug 124553 for how the query equivalent did this
+ // too soon? Do we need a new listener?
+ if (alreadyInitialized)
+ {
+ nsAbQueryLDAPMessageListener *msgListener =
+ NS_STATIC_CAST(nsAbQueryLDAPMessageListener *,
+ NS_STATIC_CAST(nsILDAPMessageListener *, mListener.get()));
+ if (msgListener)
+ {
+ msgListener->mUrl = url;
+ return msgListener->DoSearch();
+ }
+ }*/
+
+ nsCString login;
+ rv = directory->GetAuthDn(login);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t protocolVersion;
+ rv = directory->GetProtocolVersion(&protocolVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initiate LDAP message listener
+ nsAbModifyLDAPMessageListener* _messageListener =
+ new nsAbModifyLDAPMessageListener(updateType, cardDN, modArray,
+ newRDN, newBaseDN,
+ currentUrl,
+ ldapConnection,
+ serverSearchControls,
+ clientSearchControls,
+ login,
+ 0);
+ if (_messageListener == NULL)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Now lets initialize the LDAP connection properly. We'll kick
+ // off the bind operation in the callback function, |OnLDAPInit()|.
+ return ldapConnection->Init(currentUrl, login,
+ _messageListener, nullptr, protocolVersion);
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryModify.h b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.h
new file mode 100644
index 000000000..8e14b8368
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPDirectoryModify_h__
+#define nsAbLDAPDirectoryModify_h__
+
+#include "nsAbLDAPListenerBase.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsILDAPOperation.h"
+#include "nsIArray.h"
+
+class nsILDAPURL;
+
+class nsAbLDAPDirectoryModify
+{
+public:
+ nsAbLDAPDirectoryModify();
+ virtual ~nsAbLDAPDirectoryModify();
+
+protected:
+ nsresult DoModify(nsIAbLDAPDirectory *directory,
+ const int32_t &aUpdateType,
+ const nsACString &aCardDN,
+ nsIArray* modArray,
+ const nsACString &aNewRDN,
+ const nsACString &aNewBaseDN);
+};
+
+#endif // nsAbLDAPDirectoryModify_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp
new file mode 100644
index 000000000..9b22c796c
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp
@@ -0,0 +1,610 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPDirectoryQuery.h"
+#include "nsAbBoolExprToLDAPFilter.h"
+#include "nsILDAPMessage.h"
+#include "nsILDAPErrors.h"
+#include "nsILDAPOperation.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsIAbLDAPCard.h"
+#include "nsAbUtils.h"
+#include "nsAbBaseCID.h"
+#include "nsStringGlue.h"
+#include "prprf.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsAbLDAPDirectory.h"
+#include "nsAbLDAPListenerBase.h"
+#include "nsXPCOMCIDInternal.h"
+
+using namespace mozilla;
+
+// nsAbLDAPListenerBase inherits nsILDAPMessageListener
+class nsAbQueryLDAPMessageListener : public nsAbLDAPListenerBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // Note that the directoryUrl is the details of the ldap directory
+ // without any search params or return attributes specified. The searchUrl
+ // therefore has the search params and return attributes specified.
+ // nsAbQueryLDAPMessageListener(nsIAbDirectoryQuery* directoryQuery,
+ nsAbQueryLDAPMessageListener(nsIAbDirectoryQueryResultListener* resultListener,
+ nsILDAPURL* directoryUrl,
+ nsILDAPURL* searchUrl,
+ nsILDAPConnection* connection,
+ nsIAbDirectoryQueryArguments* queryArguments,
+ nsIMutableArray* serverSearchControls,
+ nsIMutableArray* clientSearchControls,
+ const nsACString &login,
+ const nsACString &mechanism,
+ const int32_t resultLimit = -1,
+ const int32_t timeOut = 0);
+
+ // nsILDAPMessageListener
+ NS_IMETHOD OnLDAPMessage(nsILDAPMessage *aMessage) override;
+
+protected:
+ virtual ~nsAbQueryLDAPMessageListener ();
+ nsresult OnLDAPMessageSearchEntry(nsILDAPMessage *aMessage);
+ nsresult OnLDAPMessageSearchResult(nsILDAPMessage *aMessage);
+
+ friend class nsAbLDAPDirectoryQuery;
+
+ nsresult Cancel();
+ virtual nsresult DoTask() override;
+ virtual void InitFailed(bool aCancelled = false) override;
+
+ nsCOMPtr<nsILDAPURL> mSearchUrl;
+ nsIAbDirectoryQueryResultListener *mResultListener;
+ int32_t mContextID;
+ nsCOMPtr<nsIAbDirectoryQueryArguments> mQueryArguments;
+ int32_t mResultLimit;
+
+ bool mFinished;
+ bool mCanceled;
+ bool mWaitingForPrevQueryToFinish;
+
+ nsCOMPtr<nsIMutableArray> mServerSearchControls;
+ nsCOMPtr<nsIMutableArray> mClientSearchControls;
+};
+
+
+NS_IMPL_ISUPPORTS(nsAbQueryLDAPMessageListener, nsILDAPMessageListener)
+
+nsAbQueryLDAPMessageListener::nsAbQueryLDAPMessageListener(
+ nsIAbDirectoryQueryResultListener *resultListener,
+ nsILDAPURL* directoryUrl,
+ nsILDAPURL* searchUrl,
+ nsILDAPConnection* connection,
+ nsIAbDirectoryQueryArguments* queryArguments,
+ nsIMutableArray* serverSearchControls,
+ nsIMutableArray* clientSearchControls,
+ const nsACString &login,
+ const nsACString &mechanism,
+ const int32_t resultLimit,
+ const int32_t timeOut) :
+ nsAbLDAPListenerBase(directoryUrl, connection, login, timeOut),
+ mSearchUrl(searchUrl),
+ mResultListener(resultListener),
+ mQueryArguments(queryArguments),
+ mResultLimit(resultLimit),
+ mFinished(false),
+ mCanceled(false),
+ mWaitingForPrevQueryToFinish(false),
+ mServerSearchControls(serverSearchControls),
+ mClientSearchControls(clientSearchControls)
+{
+ mSaslMechanism.Assign(mechanism);
+}
+
+nsAbQueryLDAPMessageListener::~nsAbQueryLDAPMessageListener ()
+{
+}
+
+nsresult nsAbQueryLDAPMessageListener::Cancel ()
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ if (mFinished || mCanceled)
+ return NS_OK;
+
+ mCanceled = true;
+ if (!mFinished)
+ mWaitingForPrevQueryToFinish = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbQueryLDAPMessageListener::OnLDAPMessage(nsILDAPMessage *aMessage)
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t messageType;
+ rv = aMessage->GetType(&messageType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool cancelOperation = false;
+
+ // Enter lock
+ {
+ MutexAutoLock lock (mLock);
+
+ if (mFinished)
+ return NS_OK;
+
+ if (messageType == nsILDAPMessage::RES_SEARCH_RESULT)
+ mFinished = true;
+ else if (mCanceled)
+ {
+ mFinished = true;
+ cancelOperation = true;
+ }
+ }
+ // Leave lock
+
+ if (!mResultListener)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!cancelOperation)
+ {
+ switch (messageType)
+ {
+ case nsILDAPMessage::RES_BIND:
+ rv = OnLDAPMessageBind(aMessage);
+ if (NS_FAILED(rv))
+ // We know the bind failed and hence the message has an error, so we
+ // can just call SearchResult with the message and that'll sort it out
+ // for us.
+ rv = OnLDAPMessageSearchResult(aMessage);
+ break;
+ case nsILDAPMessage::RES_SEARCH_ENTRY:
+ if (!mFinished && !mWaitingForPrevQueryToFinish)
+ rv = OnLDAPMessageSearchEntry(aMessage);
+ break;
+ case nsILDAPMessage::RES_SEARCH_RESULT:
+ mWaitingForPrevQueryToFinish = false;
+ rv = OnLDAPMessageSearchResult(aMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ if (mOperation)
+ rv = mOperation->AbandonExt();
+
+ rv = mResultListener->OnQueryResult(
+ nsIAbDirectoryQueryResultListener::queryResultStopped, 0);
+
+ // reset because we might re-use this listener...except don't do this
+ // until the search is done, so we'll ignore results from a previous
+ // search.
+ if (messageType == nsILDAPMessage::RES_SEARCH_RESULT)
+ mCanceled = mFinished = false;
+ }
+
+ return rv;
+}
+
+nsresult nsAbQueryLDAPMessageListener::DoTask()
+{
+ nsresult rv;
+ mCanceled = mFinished = false;
+
+ mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->Init(mConnection, this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString dn;
+ rv = mSearchUrl->GetDn(dn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t scope;
+ rv = mSearchUrl->GetScope(&scope);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString filter;
+ rv = mSearchUrl->GetFilter(filter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString attributes;
+ rv = mSearchUrl->GetAttributes(attributes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->SetServerControls(mServerSearchControls);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->SetClientControls(mClientSearchControls);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mOperation->SearchExt(dn, scope, filter, attributes, mTimeOut,
+ mResultLimit);
+}
+
+void nsAbQueryLDAPMessageListener::InitFailed(bool aCancelled)
+{
+ if (!mResultListener)
+ return;
+
+ // In the !aCancelled case we know there was an error, but we won't be
+ // able to translate it, so just return an error code of zero.
+ mResultListener->OnQueryResult(
+ aCancelled ? nsIAbDirectoryQueryResultListener::queryResultStopped :
+ nsIAbDirectoryQueryResultListener::queryResultError, 0);
+}
+
+nsresult nsAbQueryLDAPMessageListener::OnLDAPMessageSearchEntry(nsILDAPMessage *aMessage)
+{
+ nsresult rv;
+
+ if (!mResultListener)
+ return NS_ERROR_NULL_POINTER;
+
+ // the map for translating between LDAP attrs <-> addrbook fields
+ nsCOMPtr<nsISupports> iSupportsMap;
+ rv = mQueryArguments->GetTypeSpecificArg(getter_AddRefs(iSupportsMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> map = do_QueryInterface(iSupportsMap, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card = do_CreateInstance(NS_ABLDAPCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = map->SetCardPropertiesFromLDAPMessage(aMessage, card);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbLDAPCard> ldapCard = do_QueryInterface(card, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ldapCard->SetMetaProperties(aMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mResultListener->OnQueryFoundCard(card);
+}
+
+nsresult nsAbQueryLDAPMessageListener::OnLDAPMessageSearchResult(nsILDAPMessage *aMessage)
+{
+ int32_t errorCode;
+ nsresult rv = aMessage->GetErrorCode(&errorCode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED)
+ return mResultListener->OnQueryResult(
+ nsIAbDirectoryQueryResultListener::queryResultComplete, 0);
+
+ return mResultListener->OnQueryResult(
+ nsIAbDirectoryQueryResultListener::queryResultError, errorCode);
+}
+
+// nsAbLDAPDirectoryQuery
+
+NS_IMPL_ISUPPORTS(nsAbLDAPDirectoryQuery, nsIAbDirectoryQuery,
+ nsIAbDirectoryQueryResultListener)
+
+nsAbLDAPDirectoryQuery::nsAbLDAPDirectoryQuery() :
+ mInitialized(false)
+{
+}
+
+nsAbLDAPDirectoryQuery::~nsAbLDAPDirectoryQuery()
+{
+}
+
+NS_IMETHODIMP nsAbLDAPDirectoryQuery::DoQuery(nsIAbDirectory *aDirectory,
+ nsIAbDirectoryQueryArguments* aArguments,
+ nsIAbDirSearchListener* aListener,
+ int32_t aResultLimit,
+ int32_t aTimeOut,
+ int32_t* _retval)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ NS_ENSURE_ARG_POINTER(aArguments);
+
+ mListeners.AppendObject(aListener);
+
+ // Ensure existing query is stopped. Context id doesn't matter here
+ nsresult rv = StopQuery(0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitialized = true;
+
+ // Get the current directory as LDAP specific
+ nsCOMPtr<nsIAbLDAPDirectory> directory(do_QueryInterface(aDirectory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We also need the current URL to check as well...
+ nsCOMPtr<nsILDAPURL> currentUrl;
+ rv = directory->GetLDAPURL(getter_AddRefs(currentUrl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString login;
+ rv = directory->GetAuthDn(login);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString saslMechanism;
+ rv = directory->GetSaslMechanism(saslMechanism);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t protocolVersion;
+ rv = directory->GetProtocolVersion(&protocolVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // To do:
+ // Ensure query is stopped
+ // If connection params have changed re-create connection
+ // else reuse existing connection
+
+ bool redoConnection = false;
+
+ if (!mConnection || !mDirectoryUrl)
+ {
+ mDirectoryUrl = currentUrl;
+ aDirectory->GetUuid(mDirectoryId);
+ mCurrentLogin = login;
+ mCurrentMechanism = saslMechanism;
+ mCurrentProtocolVersion = protocolVersion;
+ redoConnection = true;
+ }
+ else
+ {
+ bool equal;
+ rv = mDirectoryUrl->Equals(currentUrl, &equal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!equal)
+ {
+ mDirectoryUrl = currentUrl;
+ aDirectory->GetUuid(mDirectoryId);
+ mCurrentLogin = login;
+ mCurrentMechanism = saslMechanism;
+ mCurrentProtocolVersion = protocolVersion;
+ redoConnection = true;
+ }
+ else
+ {
+ // Has login or version changed?
+ if (login != mCurrentLogin ||
+ saslMechanism != mCurrentMechanism ||
+ protocolVersion != mCurrentProtocolVersion)
+ {
+ redoConnection = true;
+ mCurrentLogin = login;
+ mCurrentMechanism = saslMechanism;
+ mCurrentProtocolVersion = protocolVersion;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = mDirectoryUrl->Clone(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILDAPURL> url(do_QueryInterface(uri, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get/Set the return attributes
+ nsCOMPtr<nsISupports> iSupportsMap;
+ rv = aArguments->GetTypeSpecificArg(getter_AddRefs(iSupportsMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> map = do_QueryInterface(iSupportsMap, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Require all attributes that are mapped to card properties
+ nsAutoCString returnAttributes;
+ rv = map->GetAllCardAttributes(returnAttributes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetAttributes(returnAttributes);
+ // Now do the error check
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Also require the objectClass attribute, it is used by
+ // nsAbLDAPCard::SetMetaProperties
+ rv = url->AddAttribute(NS_LITERAL_CSTRING("objectClass"));
+
+ nsAutoCString filter;
+
+ // Get filter from arguments if set:
+ rv = aArguments->GetFilter(filter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (filter.IsEmpty()) {
+ // Get the filter
+ nsCOMPtr<nsISupports> supportsExpression;
+ rv = aArguments->GetExpression(getter_AddRefs(supportsExpression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression(do_QueryInterface(supportsExpression, &rv));
+
+ // figure out how we map attribute names to addressbook fields for this
+ // query
+ rv = nsAbBoolExprToLDAPFilter::Convert(map, expression, filter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ /*
+ * Mozilla itself cannot arrive here with a blank filter
+ * as the nsAbLDAPDirectory::StartSearch() disallows it.
+ * But 3rd party LDAP query integration with Mozilla begins
+ * in this method.
+ *
+ * Default the filter string if blank, otherwise it gets
+ * set to (objectclass=*) which returns everything. Set
+ * the default to (objectclass=inetorgperson) as this
+ * is the most appropriate default objectclass which is
+ * central to the makeup of the mozilla ldap address book
+ * entries.
+ */
+ if (filter.IsEmpty())
+ {
+ filter.AssignLiteral("(objectclass=inetorgperson)");
+ }
+
+ // get the directoryFilter from the directory url and merge it with the user's
+ // search filter
+ nsAutoCString urlFilter;
+ rv = mDirectoryUrl->GetFilter(urlFilter);
+
+ // if urlFilter is unset (or set to the default "objectclass=*"), there's
+ // no need to AND in an empty search term, so leave prefix and suffix empty
+
+ nsAutoCString searchFilter;
+ if (urlFilter.Length() && !urlFilter.EqualsLiteral("(objectclass=*)"))
+ {
+ // if urlFilter isn't parenthesized, we need to add in parens so that
+ // the filter works as a term to &
+ //
+ if (urlFilter[0] != '(')
+ {
+ searchFilter = NS_LITERAL_CSTRING("(&(");
+ searchFilter.Append(urlFilter);
+ searchFilter.AppendLiteral(")");
+ }
+ else
+ {
+ searchFilter = NS_LITERAL_CSTRING("(&");
+ searchFilter.Append(urlFilter);
+ }
+
+ searchFilter += filter;
+ searchFilter += ')';
+ }
+ else
+ searchFilter = filter;
+
+ rv = url->SetFilter(searchFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now formulate the search string
+
+ // Get the scope
+ int32_t scope;
+ bool doSubDirectories;
+ rv = aArguments->GetQuerySubDirectories (&doSubDirectories);
+ NS_ENSURE_SUCCESS(rv, rv);
+ scope = doSubDirectories ? nsILDAPURL::SCOPE_SUBTREE :
+ nsILDAPURL::SCOPE_ONELEVEL;
+
+ rv = url->SetScope(scope);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // too soon? Do we need a new listener?
+ // If we already have a connection, and don't need to re-do it, give it the
+ // new search details and go for it...
+ if (!redoConnection)
+ {
+ nsAbQueryLDAPMessageListener *msgListener =
+ static_cast<nsAbQueryLDAPMessageListener *>(static_cast<nsILDAPMessageListener *>(mListener.get()));
+ if (msgListener)
+ {
+ // Ensure the urls are correct
+ msgListener->mDirectoryUrl = mDirectoryUrl;
+ msgListener->mSearchUrl = url;
+ // Also ensure we set the correct result limit
+ msgListener->mResultLimit = aResultLimit;
+ return msgListener->DoTask();
+ }
+ }
+
+ nsCOMPtr<nsIAbLDAPDirectory> abLDAPDir = do_QueryInterface(aDirectory, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> serverSearchControls;
+ rv = abLDAPDir->GetSearchServerControls(getter_AddRefs(serverSearchControls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> clientSearchControls;
+ rv = abLDAPDir->GetSearchClientControls(getter_AddRefs(clientSearchControls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the new connection (which cause the old one to be dropped if necessary)
+ mConnection = do_CreateInstance(NS_LDAPCONNECTION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectoryQueryResultListener> resultListener =
+ do_QueryInterface((nsIAbDirectoryQuery*)this, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initiate LDAP message listener
+ nsAbQueryLDAPMessageListener* _messageListener =
+ new nsAbQueryLDAPMessageListener(resultListener, mDirectoryUrl, url,
+ mConnection, aArguments,
+ serverSearchControls, clientSearchControls,
+ mCurrentLogin, mCurrentMechanism,
+ aResultLimit, aTimeOut);
+ if (_messageListener == NULL)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mListener = _messageListener;
+ *_retval = 1;
+
+ // Now lets initialize the LDAP connection properly. We'll kick
+ // off the bind operation in the callback function, |OnLDAPInit()|.
+ rv = mConnection->Init(mDirectoryUrl, mCurrentLogin,
+ mListener, nullptr, mCurrentProtocolVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+/* void stopQuery (in long contextID); */
+NS_IMETHODIMP nsAbLDAPDirectoryQuery::StopQuery(int32_t contextID)
+{
+ mInitialized = true;
+
+ if (!mListener)
+ return NS_OK;
+
+ nsAbQueryLDAPMessageListener *listener =
+ static_cast<nsAbQueryLDAPMessageListener *>(static_cast<nsILDAPMessageListener *>(mListener.get()));
+ if (listener)
+ return listener->Cancel();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectoryQuery::OnQueryFoundCard(nsIAbCard *aCard)
+{
+ aCard->SetDirectoryId(mDirectoryId);
+
+ for (int32_t i = 0; i < mListeners.Count(); ++i)
+ mListeners[i]->OnSearchFoundCard(aCard);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectoryQuery::OnQueryResult(int32_t aResult,
+ int32_t aErrorCode)
+{
+ uint32_t count = mListeners.Count();
+
+ // XXX: Temporary fix for crasher needs reviewing as part of bug 135231.
+ // Temporarily add a reference to ourselves, in case the only thing
+ // keeping us alive is the link with the listener.
+ NS_ADDREF_THIS();
+
+ for (int32_t i = count - 1; i >= 0; --i)
+ {
+ mListeners[i]->OnSearchFinished(aResult, EmptyString());
+ mListeners.RemoveObjectAt(i);
+ }
+
+ NS_RELEASE_THIS();
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h
new file mode 100644
index 000000000..2c11c2ab9
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPDirectoryQuery_h__
+#define nsAbLDAPDirectoryQuery_h__
+
+#include "nsIAbDirectoryQuery.h"
+#include "nsILDAPConnection.h"
+#include "nsILDAPMessageListener.h"
+#include "nsILDAPURL.h"
+#include "nsWeakReference.h"
+
+#include "nsStringGlue.h"
+#include "nsCOMArray.h"
+
+class nsAbLDAPDirectoryQuery : public nsIAbDirectoryQuery,
+ public nsIAbDirectoryQueryResultListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERY
+ NS_DECL_NSIABDIRECTORYQUERYRESULTLISTENER
+
+ nsAbLDAPDirectoryQuery();
+
+protected:
+ nsCOMPtr<nsILDAPMessageListener> mListener;
+
+private:
+ virtual ~nsAbLDAPDirectoryQuery();
+ nsCOMPtr<nsILDAPConnection> mConnection;
+ nsCOMPtr<nsILDAPURL> mDirectoryUrl;
+ nsCString mDirectoryId;
+ nsCOMArray<nsIAbDirSearchListener> mListeners;
+ nsCString mCurrentLogin;
+ nsCString mCurrentMechanism;
+ uint32_t mCurrentProtocolVersion;
+
+ bool mInitialized;
+};
+
+#endif // nsAbLDAPDirectoryQuery_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPListenerBase.cpp b/mailnews/addrbook/src/nsAbLDAPListenerBase.cpp
new file mode 100644
index 000000000..e5465a7f5
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPListenerBase.cpp
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPListenerBase.h"
+#include "nsIWindowWatcher.h"
+#include "nsIWindowMediator.h"
+#include "mozIDOMWindow.h"
+#include "nsIAuthPrompt.h"
+#include "nsIStringBundle.h"
+#include "nsILDAPMessage.h"
+#include "nsILDAPErrors.h"
+#include "nsILoginManager.h"
+#include "nsILoginInfo.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+nsAbLDAPListenerBase::nsAbLDAPListenerBase(nsILDAPURL* url,
+ nsILDAPConnection* connection,
+ const nsACString &login,
+ const int32_t timeOut) :
+ mDirectoryUrl(url), mConnection(connection), mLogin(login),
+ mTimeOut(timeOut), mBound(false), mInitialized(false),
+ mLock("nsAbLDAPListenerBase.mLock")
+{
+}
+
+nsAbLDAPListenerBase::~nsAbLDAPListenerBase()
+{
+}
+
+nsresult nsAbLDAPListenerBase::Initiate()
+{
+ if (!mConnection || !mDirectoryUrl)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mInitialized)
+ return NS_OK;
+
+ mInitialized = true;
+
+ return NS_OK;
+}
+
+// If something fails in this function, we must call InitFailed() so that the
+// derived class (and listener) knows to cancel what its doing as there is
+// a problem.
+NS_IMETHODIMP nsAbLDAPListenerBase::OnLDAPInit(nsILDAPConnection *aConn, nsresult aStatus)
+{
+ if (!mConnection || !mDirectoryUrl)
+ {
+ InitFailed();
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsresult rv;
+ nsString passwd;
+
+ // Make sure that the Init() worked properly
+ if (NS_FAILED(aStatus))
+ {
+ InitFailed();
+ return NS_OK;
+ }
+
+ // If mLogin is set, we're expected to use it to get a password.
+ //
+ if (!mLogin.IsEmpty() && !mSaslMechanism.EqualsLiteral("GSSAPI"))
+ {
+ // get the string bundle service
+ //
+ nsCOMPtr<nsIStringBundleService> stringBundleSvc =
+ mozilla::services::GetStringBundleService();
+ if (!stringBundleSvc)
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ " error getting string bundle service");
+ InitFailed();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // get the LDAP string bundle
+ //
+ nsCOMPtr<nsIStringBundle> ldapBundle;
+ rv = stringBundleSvc->CreateBundle("chrome://mozldap/locale/ldap.properties",
+ getter_AddRefs(ldapBundle));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit(): error creating string"
+ "bundle chrome://mozldap/locale/ldap.properties");
+ InitFailed();
+ return rv;
+ }
+
+ // get the title for the authentication prompt
+ //
+ nsString authPromptTitle;
+ rv = ldapBundle->GetStringFromName(u"authPromptTitle",
+ getter_Copies(authPromptTitle));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit(): error getting"
+ "'authPromptTitle' string from bundle "
+ "chrome://mozldap/locale/ldap.properties");
+ InitFailed();
+ return rv;
+ }
+
+ // get the host name for the auth prompt
+ //
+ nsAutoCString host;
+ rv = mDirectoryUrl->GetAsciiHost(host);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit(): error getting ascii host"
+ "name from directory url");
+ InitFailed();
+ return rv;
+ }
+
+ // hostTemp is only necessary to work around a code-generation
+ // bug in egcs 1.1.2 (the version of gcc that comes with Red Hat 6.2),
+ // which is the default compiler for Mozilla on linux at the moment.
+ //
+ NS_ConvertASCIItoUTF16 hostTemp(host);
+ const char16_t *hostArray[1] = { hostTemp.get() };
+
+ // format the hostname into the authprompt text string
+ //
+ nsString authPromptText;
+ rv = ldapBundle->FormatStringFromName(u"authPromptText",
+ hostArray,
+ sizeof(hostArray) / sizeof(const char16_t *),
+ getter_Copies(authPromptText));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ "error getting 'authPromptText' string from bundle "
+ "chrome://mozldap/locale/ldap.properties");
+ InitFailed();
+ return rv;
+ }
+
+ // get the window mediator service, so we can get an auth prompter
+ //
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ " couldn't get window mediator service.");
+ InitFailed();
+ return rv;
+ }
+
+ // get the addressbook window, as it will be used to parent the auth
+ // prompter dialog
+ //
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ rv = windowMediator->GetMostRecentWindow(nullptr,
+ getter_AddRefs(window));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ " error getting most recent window");
+ InitFailed();
+ return rv;
+ }
+
+ // get the window watcher service, so we can get an auth prompter
+ //
+ nsCOMPtr<nsIWindowWatcher> windowWatcherSvc =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ " couldn't get window watcher service.");
+ InitFailed();
+ return rv;
+ }
+
+ // get the auth prompter itself
+ //
+ nsCOMPtr<nsIAuthPrompt> authPrompter;
+ rv = windowWatcherSvc->GetNewAuthPrompter(window,
+ getter_AddRefs(authPrompter));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit():"
+ " error getting auth prompter");
+ InitFailed();
+ return rv;
+ }
+
+ // get authentication password, prompting the user if necessary
+ //
+ // we're going to use the URL spec of the server as the "realm" for
+ // wallet to remember the password by / for.
+
+ // Get the specification
+ nsCString spec;
+ rv = mDirectoryUrl->GetSpec(spec);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit():"
+ " error getting directory url spec");
+ InitFailed();
+ return rv;
+ }
+
+ bool status;
+ rv = authPrompter->PromptPassword(authPromptTitle.get(),
+ authPromptText.get(),
+ NS_ConvertUTF8toUTF16(spec).get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ getter_Copies(passwd),
+ &status);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to prompt for"
+ " password");
+ InitFailed();
+ return rv;
+ }
+ else if (!status)
+ {
+ InitFailed(true);
+ return NS_OK;
+ }
+ }
+
+ // Initiate the LDAP operation
+ mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to create ldap operation");
+ InitFailed();
+ return rv;
+ }
+
+ rv = mOperation->Init(mConnection, this, nullptr);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to Initialise operation");
+ InitFailed();
+ return rv;
+ }
+
+ // Try non-password mechanisms first
+ if (mSaslMechanism.EqualsLiteral("GSSAPI"))
+ {
+ nsAutoCString service;
+ rv = mDirectoryUrl->GetAsciiHost(service);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ service.Insert(NS_LITERAL_CSTRING("ldap@"), 0);
+
+ nsCOMPtr<nsIAuthModule> authModule =
+ do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sasl-gssapi", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->SaslBind(service, mSaslMechanism, authModule);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): "
+ "failed to perform GSSAPI bind");
+ mOperation = nullptr; // Break Listener -> Operation -> Listener ref cycle
+ InitFailed();
+ }
+ return rv;
+ }
+
+ // Bind
+ rv = mOperation->SimpleBind(NS_ConvertUTF16toUTF8(passwd));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to perform bind operation");
+ mOperation = nullptr; // Break Listener->Operation->Listener reference cycle
+ InitFailed();
+ }
+ return rv;
+}
+
+nsresult nsAbLDAPListenerBase::OnLDAPMessageBind(nsILDAPMessage *aMessage)
+{
+ if (mBound)
+ return NS_OK;
+
+ // see whether the bind actually succeeded
+ //
+ int32_t errCode;
+ nsresult rv = aMessage->GetErrorCode(&errCode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (errCode != nsILDAPErrors::SUCCESS)
+ {
+ // if the login failed, tell the wallet to forget this password
+ //
+ if (errCode == nsILDAPErrors::INAPPROPRIATE_AUTH ||
+ errCode == nsILDAPErrors::INVALID_CREDENTIALS)
+ {
+ // Login failed, so try again - but first remove the existing login(s)
+ // so that the user gets prompted. This may not be the best way of doing
+ // things, we need to review that later.
+
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString spec;
+ rv = mDirectoryUrl->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString prePath;
+ rv = mDirectoryUrl->GetPrePath(prePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ nsILoginInfo** logins;
+
+ rv = loginMgr->FindLogins(&count, NS_ConvertUTF8toUTF16(prePath),
+ EmptyString(),
+ NS_ConvertUTF8toUTF16(spec), &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Typically there should only be one-login stored for this url, however,
+ // just in case there isn't.
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ rv = loginMgr->RemoveLogin(logins[i]);
+ if (NS_FAILED(rv))
+ {
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
+ return rv;
+ }
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
+
+ // XXX We should probably pop up an error dialog telling
+ // the user that the login failed here, rather than just bringing
+ // up the password dialog again, which is what calling OnLDAPInit()
+ // does.
+ return OnLDAPInit(nullptr, NS_OK);
+ }
+
+ // Don't know how to handle this, so use the message error code in
+ // the failure return value so we hopefully get it back to the UI.
+ return NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_LDAP, errCode);
+ }
+
+ mBound = true;
+ return DoTask();
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPListenerBase.h b/mailnews/addrbook/src/nsAbLDAPListenerBase.h
new file mode 100644
index 000000000..aa292df71
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPListenerBase.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPListenerBase_h__
+#define nsAbLDAPListenerBase_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsILDAPMessageListener.h"
+#include "nsILDAPURL.h"
+#include "nsILDAPConnection.h"
+#include "nsILDAPOperation.h"
+#include "nsStringGlue.h"
+#include "mozilla/Mutex.h"
+
+class nsAbLDAPListenerBase : public nsILDAPMessageListener
+{
+public:
+ // Note that the directoryUrl is the details of the ldap directory
+ // without any search params or attributes specified.
+ nsAbLDAPListenerBase(nsILDAPURL* directoryUrl = nullptr,
+ nsILDAPConnection* connection = nullptr,
+ const nsACString &login = EmptyCString(),
+ const int32_t timeOut = 0);
+ virtual ~nsAbLDAPListenerBase();
+
+ NS_IMETHOD OnLDAPInit(nsILDAPConnection *aConn, nsresult aStatus) override;
+
+protected:
+ nsresult OnLDAPMessageBind(nsILDAPMessage *aMessage);
+
+ nsresult Initiate();
+
+ // Called if an LDAP initialization fails.
+ virtual void InitFailed(bool aCancelled = false) = 0;
+
+ // Called to start off the required task after a bind.
+ virtual nsresult DoTask() = 0;
+
+ nsCOMPtr<nsILDAPURL> mDirectoryUrl;
+ nsCOMPtr<nsILDAPOperation> mOperation; // current ldap op
+ nsILDAPConnection* mConnection;
+ nsCString mLogin;
+ nsCString mSaslMechanism;
+ int32_t mTimeOut;
+ bool mBound;
+ bool mInitialized;
+
+ mozilla::Mutex mLock;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationData.cpp b/mailnews/addrbook/src/nsAbLDAPReplicationData.cpp
new file mode 100644
index 000000000..faa8cdd23
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationData.cpp
@@ -0,0 +1,489 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsILDAPMessage.h"
+#include "nsAbLDAPReplicationData.h"
+#include "nsIAbCard.h"
+#include "nsAbBaseCID.h"
+#include "nsAbUtils.h"
+#include "nsAbLDAPReplicationQuery.h"
+#include "nsILDAPErrors.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+
+// once bug # 101252 gets fixed, this should be reverted back to be non threadsafe
+// implementation is not really thread safe since each object should exist
+// independently along with its related independent nsAbLDAPReplicationQuery object.
+NS_IMPL_ISUPPORTS(nsAbLDAPProcessReplicationData, nsIAbLDAPProcessReplicationData, nsILDAPMessageListener)
+
+nsAbLDAPProcessReplicationData::nsAbLDAPProcessReplicationData() :
+ nsAbLDAPListenerBase(),
+ mState(kIdle),
+ mProtocol(-1),
+ mCount(0),
+ mDBOpen(false),
+ mInitialized(false)
+{
+}
+
+nsAbLDAPProcessReplicationData::~nsAbLDAPProcessReplicationData()
+{
+ /* destructor code */
+ if(mDBOpen && mReplicationDB)
+ mReplicationDB->Close(false);
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::Init(
+ nsIAbLDAPDirectory *aDirectory,
+ nsILDAPConnection *aConnection,
+ nsILDAPURL* aURL,
+ nsIAbLDAPReplicationQuery *aQuery,
+ nsIWebProgressListener *aProgressListener)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_ENSURE_ARG_POINTER(aConnection);
+ NS_ENSURE_ARG_POINTER(aURL);
+ NS_ENSURE_ARG_POINTER(aQuery);
+
+ mDirectory = aDirectory;
+ mConnection = aConnection;
+ mDirectoryUrl = aURL;
+ mQuery = aQuery;
+
+ mListener = aProgressListener;
+
+ nsresult rv = mDirectory->GetAttributeMap(getter_AddRefs(mAttrMap));
+ if (NS_FAILED(rv)) {
+ mQuery = nullptr;
+ return rv;
+ }
+
+ rv = mDirectory->GetAuthDn(mLogin);
+ if (NS_FAILED(rv)) {
+ mQuery = nullptr;
+ return rv;
+ }
+
+ rv = mDirectory->GetSaslMechanism(mSaslMechanism);
+ if (NS_FAILED(rv)) {
+ mQuery = nullptr;
+ return rv;
+ }
+
+ mInitialized = true;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::GetReplicationState(int32_t *aReplicationState)
+{
+ NS_ENSURE_ARG_POINTER(aReplicationState);
+ *aReplicationState = mState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::GetProtocolUsed(int32_t *aProtocolUsed)
+{
+ NS_ENSURE_ARG_POINTER(aProtocolUsed);
+ *aProtocolUsed = mProtocol;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::OnLDAPMessage(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t messageType;
+ nsresult rv = aMessage->GetType(&messageType);
+ if (NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+
+ switch (messageType)
+ {
+ case nsILDAPMessage::RES_BIND:
+ rv = OnLDAPMessageBind(aMessage);
+ if (NS_FAILED(rv))
+ rv = Abort();
+ break;
+ case nsILDAPMessage::RES_SEARCH_ENTRY:
+ rv = OnLDAPSearchEntry(aMessage);
+ break;
+ case nsILDAPMessage::RES_SEARCH_RESULT:
+ rv = OnLDAPSearchResult(aMessage);
+ break;
+ default:
+ // for messageTypes we do not handle return NS_OK to LDAP and move ahead.
+ rv = NS_OK;
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::Abort()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+
+ if (mState != kIdle && mOperation) {
+ rv = mOperation->AbandonExt();
+ if (NS_SUCCEEDED(rv))
+ mState = kIdle;
+ }
+
+ if (mReplicationDB && mDBOpen) {
+ // force close since we need to delete the file.
+ mReplicationDB->ForceClosed();
+ mDBOpen = false;
+
+ // delete the unsaved replication file
+ if (mReplicationFile) {
+ rv = mReplicationFile->Remove(false);
+ if (NS_SUCCEEDED(rv) && mDirectory) {
+ nsAutoCString fileName;
+ rv = mDirectory->GetReplicationFileName(fileName);
+ // now put back the backed up replicated file if aborted
+ if (NS_SUCCEEDED(rv) && mBackupReplicationFile)
+ rv = mBackupReplicationFile->MoveToNative(nullptr, fileName);
+ }
+ }
+ }
+
+ Done(false);
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessReplicationData::DoTask()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = OpenABForReplicatedDir(true);
+ if (NS_FAILED(rv))
+ // do not call done here since it is called by OpenABForReplicationDir
+ return rv;
+
+ mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->Init(mConnection, this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the relevant attributes associated with the directory server url
+ nsAutoCString urlFilter;
+ rv = mDirectoryUrl->GetFilter(urlFilter);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString dn;
+ rv = mDirectoryUrl->GetDn(dn);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (dn.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ int32_t scope;
+ rv = mDirectoryUrl->GetScope(&scope);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString attributes;
+ rv = mDirectoryUrl->GetAttributes(attributes);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mState = kReplicatingAll;
+
+ if (mListener && NS_SUCCEEDED(rv))
+ // XXX Cast from bool to nsresult
+ mListener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_START,
+ static_cast<nsresult>(true));
+
+ return mOperation->SearchExt(dn, scope, urlFilter, attributes, 0, 0);
+}
+
+void nsAbLDAPProcessReplicationData::InitFailed(bool aCancelled)
+{
+ // Just call Done() which will ensure everything is tidied up nicely.
+ Done(false);
+}
+
+nsresult nsAbLDAPProcessReplicationData::OnLDAPSearchEntry(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+ // since this runs on the main thread and is single threaded, this will
+ // take care of entries returned by LDAP Connection thread after Abort.
+ if (!mReplicationDB || !mDBOpen)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+
+ // Although we would may naturally create an nsIAbLDAPCard here, we don't
+ // need to as we are writing this straight to the database, so just create
+ // the database version instead.
+ nsCOMPtr<nsIAbCard> newCard(do_CreateInstance(NS_ABMDBCARD_CONTRACTID,
+ &rv));
+ if (NS_FAILED(rv)) {
+ Abort();
+ return rv;
+ }
+
+ rv = mAttrMap->SetCardPropertiesFromLDAPMessage(aMessage, newCard);
+ if (NS_FAILED(rv))
+ {
+ NS_WARNING("nsAbLDAPProcessReplicationData::OnLDAPSearchEntry"
+ "No card properties could be set");
+ // if some entries are bogus for us, continue with next one
+ return NS_OK;
+ }
+
+ rv = mReplicationDB->CreateNewCardAndAddToDB(newCard, false, nullptr);
+ if(NS_FAILED(rv)) {
+ Abort();
+ return rv;
+ }
+
+ // now set the attribute for the DN of the entry in the card in the DB
+ nsAutoCString authDN;
+ rv = aMessage->GetDn(authDN);
+ if(NS_SUCCEEDED(rv) && !authDN.IsEmpty())
+ {
+ newCard->SetPropertyAsAUTF8String("_DN", authDN);
+ }
+
+ rv = mReplicationDB->EditCard(newCard, false, nullptr);
+ if(NS_FAILED(rv)) {
+ Abort();
+ return rv;
+ }
+
+
+ mCount ++;
+
+ if (mListener && !(mCount % 10)) // inform the listener every 10 entries
+ {
+ mListener->OnProgressChange(nullptr,nullptr,mCount, -1, mCount, -1);
+ // in case if the LDAP Connection thread is starved and causes problem
+ // uncomment this one and try.
+ // PR_Sleep(PR_INTERVAL_NO_WAIT); // give others a chance
+ }
+
+ return rv;
+}
+
+
+nsresult nsAbLDAPProcessReplicationData::OnLDAPSearchResult(nsILDAPMessage *aMessage)
+{
+#ifdef DEBUG_rdayal
+ printf("LDAP Replication : Got Results for Completion");
+#endif
+
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t errorCode;
+ nsresult rv = aMessage->GetErrorCode(&errorCode);
+
+ if(NS_SUCCEEDED(rv)) {
+ // We are done with the LDAP search for all entries.
+ if(errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED) {
+ Done(true);
+ if(mReplicationDB && mDBOpen) {
+ rv = mReplicationDB->Close(true);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close on Success failed");
+ mDBOpen = false;
+ // once we have saved the new replication file, delete the backup file
+ if(mBackupReplicationFile)
+ {
+ rv = mBackupReplicationFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed");
+ }
+ }
+ return NS_OK;
+ }
+ }
+
+ // in case if GetErrorCode returned error or errorCode is not SUCCESS / SIZELIMIT_EXCEEDED
+ if(mReplicationDB && mDBOpen) {
+ // if error result is returned close the DB without saving ???
+ // should we commit anyway ??? whatever is returned is not lost then !!
+ rv = mReplicationDB->ForceClosed(); // force close since we need to delete the file.
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB ForceClosed on Failure failed");
+ mDBOpen = false;
+ // if error result is returned remove the replicated file
+ if(mReplicationFile) {
+ rv = mReplicationFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication File Remove on Failure failed");
+ if(NS_SUCCEEDED(rv)) {
+ // now put back the backed up replicated file
+ if(mBackupReplicationFile && mDirectory)
+ {
+ nsAutoCString fileName;
+ rv = mDirectory->GetReplicationFileName(fileName);
+ if (NS_SUCCEEDED(rv) && !fileName.IsEmpty())
+ {
+ rv = mBackupReplicationFile->MoveToNative(nullptr, fileName);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication Backup File Move back on Failure failed");
+ }
+ }
+ }
+ }
+ Done(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAbLDAPProcessReplicationData::OpenABForReplicatedDir(bool aCreate)
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mDirectory->GetReplicationFile(getter_AddRefs(mReplicationFile));
+ if (NS_FAILED(rv))
+ {
+ Done(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString fileName;
+ rv = mReplicationFile->GetNativeLeafName(fileName);
+ if (NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+
+ // if the AB DB already exists backup existing one,
+ // in case if the user cancels or Abort put back the backed up file
+ bool fileExists;
+ rv = mReplicationFile->Exists(&fileExists);
+ if(NS_SUCCEEDED(rv) && fileExists) {
+ // create the backup file object same as the Replication file object.
+ // we create a backup file here since we need to cleanup the existing file
+ // for create and then commit so instead of deleting existing cards we just
+ // clone the existing one for a much better performance - for Download All.
+ // And also important in case if replication fails we donot lose user's existing
+ // replicated data for both Download all and Changelog.
+ nsCOMPtr<nsIFile> clone;
+ rv = mReplicationFile->Clone(getter_AddRefs(clone));
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ mBackupReplicationFile = do_QueryInterface(clone, &rv);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ rv = mBackupReplicationFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0777);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ nsAutoString backupFileLeafName;
+ rv = mBackupReplicationFile->GetLeafName(backupFileLeafName);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ // remove the newly created unique backup file so that move and copy succeeds.
+ rv = mBackupReplicationFile->Remove(false);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+
+ if(aCreate) {
+ // set backup file to existing replication file for move
+ mBackupReplicationFile->SetNativeLeafName(fileName);
+
+ rv = mBackupReplicationFile->MoveTo(nullptr, backupFileLeafName);
+ // set the backup file leaf name now
+ if (NS_SUCCEEDED(rv))
+ mBackupReplicationFile->SetLeafName(backupFileLeafName);
+ }
+ else {
+ // set backup file to existing replication file for copy
+ mBackupReplicationFile->SetNativeLeafName(fileName);
+
+ // specify the parent here specifically,
+ // passing nullptr to copy to the same dir actually renames existing file
+ // instead of making another copy of the existing file.
+ nsCOMPtr<nsIFile> parent;
+ rv = mBackupReplicationFile->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv))
+ rv = mBackupReplicationFile->CopyTo(parent, backupFileLeafName);
+ // set the backup file leaf name now
+ if (NS_SUCCEEDED(rv))
+ mBackupReplicationFile->SetLeafName(backupFileLeafName);
+ }
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory =
+ do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+ if(NS_FAILED(rv)) {
+ if (mBackupReplicationFile)
+ mBackupReplicationFile->Remove(false);
+ Done(false);
+ return rv;
+ }
+
+ rv = addrDBFactory->Open(mReplicationFile, aCreate, true, getter_AddRefs(mReplicationDB));
+ if(NS_FAILED(rv)) {
+ Done(false);
+ if (mBackupReplicationFile)
+ mBackupReplicationFile->Remove(false);
+ return rv;
+ }
+
+ mDBOpen = true; // replication DB is now Open
+ return rv;
+}
+
+void nsAbLDAPProcessReplicationData::Done(bool aSuccess)
+{
+ if (!mInitialized)
+ return;
+
+ mState = kReplicationDone;
+
+ if (mQuery)
+ mQuery->Done(aSuccess);
+
+ if (mListener)
+ // XXX Cast from bool to nsresult
+ mListener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP,
+ static_cast<nsresult>(aSuccess));
+
+ // since this is called when all is done here, either on success,
+ // failure or abort release the query now.
+ mQuery = nullptr;
+}
+
+nsresult nsAbLDAPProcessReplicationData::DeleteCard(nsString & aDn)
+{
+ nsCOMPtr<nsIAbCard> cardToDelete;
+ mReplicationDB->GetCardFromAttribute(nullptr, "_DN", NS_ConvertUTF16toUTF8(aDn),
+ false, getter_AddRefs(cardToDelete));
+ return mReplicationDB->DeleteCard(cardToDelete, false, nullptr);
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationData.h b/mailnews/addrbook/src/nsAbLDAPReplicationData.h
new file mode 100644
index 000000000..546ce659e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationData.h
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsAbLDAPReplicationData_h__
+#define nsAbLDAPReplicationData_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIAbLDAPReplicationData.h"
+#include "nsIWebProgressListener.h"
+#include "nsIAbLDAPReplicationQuery.h"
+#include "nsAbLDAPListenerBase.h"
+#include "nsIAddrDatabase.h"
+#include "nsIFile.h"
+#include "nsDirPrefs.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsStringGlue.h"
+
+class nsAbLDAPProcessReplicationData : public nsIAbLDAPProcessReplicationData,
+ public nsAbLDAPListenerBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABLDAPPROCESSREPLICATIONDATA
+
+ nsAbLDAPProcessReplicationData();
+
+ // nsILDAPMessageListener
+ NS_IMETHOD OnLDAPMessage(nsILDAPMessage *aMessage) override;
+
+protected:
+ virtual ~nsAbLDAPProcessReplicationData();
+ virtual nsresult DoTask() override;
+ virtual void InitFailed(bool aCancelled = false) override;
+
+ // pointer to the interfaces used by this object
+ nsCOMPtr<nsIWebProgressListener> mListener;
+ // pointer to the query to call back to once we've finished
+ nsCOMPtr<nsIAbLDAPReplicationQuery> mQuery;
+
+ nsCOMPtr<nsIAddrDatabase> mReplicationDB;
+ nsCOMPtr <nsIFile> mReplicationFile;
+ nsCOMPtr <nsIFile> mBackupReplicationFile;
+
+ // state of processing, protocol used and count of results
+ int32_t mState;
+ int32_t mProtocol;
+ int32_t mCount;
+ bool mDBOpen;
+ bool mInitialized;
+
+ nsCOMPtr<nsIAbLDAPDirectory> mDirectory;
+ nsCOMPtr<nsIAbLDAPAttributeMap> mAttrMap; // maps ab properties to ldap attrs
+
+ virtual nsresult OnLDAPSearchEntry(nsILDAPMessage *aMessage);
+ virtual nsresult OnLDAPSearchResult(nsILDAPMessage *aMessage);
+
+ nsresult OpenABForReplicatedDir(bool bCreate);
+ nsresult DeleteCard(nsString & aDn);
+ void Done(bool aSuccess);
+};
+
+
+#endif // nsAbLDAPReplicationData_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp
new file mode 100644
index 000000000..d82a8336c
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsCOMPtr.h"
+#include "nsAbLDAPReplicationQuery.h"
+#include "nsAbLDAPReplicationService.h"
+#include "nsAbLDAPReplicationData.h"
+#include "nsILDAPURL.h"
+#include "nsAbBaseCID.h"
+#include "nsAbUtils.h"
+#include "nsDirPrefs.h"
+#include "prmem.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS(nsAbLDAPReplicationQuery,
+ nsIAbLDAPReplicationQuery)
+
+nsAbLDAPReplicationQuery::nsAbLDAPReplicationQuery()
+ : mInitialized(false)
+{
+}
+
+nsresult nsAbLDAPReplicationQuery::InitLDAPData()
+{
+ nsAutoCString fileName;
+ nsresult rv = mDirectory->GetReplicationFileName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this is done here to take care of the problem related to bug # 99124.
+ // earlier versions of Mozilla could have the fileName associated with the directory
+ // to be abook.mab which is the profile's personal addressbook. If the pref points to
+ // it, calls nsDirPrefs to generate a new server filename.
+ if (fileName.IsEmpty() || fileName.EqualsLiteral(kPersonalAddressbook))
+ {
+ // Ensure fileName is empty for DIR_GenerateAbFileName to work
+ // correctly.
+ fileName.Truncate();
+
+ nsCOMPtr<nsIAbDirectory> standardDir(do_QueryInterface(mDirectory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString dirPrefId;
+ rv = standardDir->GetDirPrefId(dirPrefId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX This should be replaced by a local function at some stage.
+ // For now we'll continue using the nsDirPrefs version.
+ DIR_Server* server = DIR_GetServerFromList(dirPrefId.get());
+ if (server)
+ {
+ DIR_SetServerFileName(server);
+ // Now ensure the prefs are saved
+ DIR_SavePrefsForOneServer(server);
+ }
+ }
+
+ rv = mDirectory->SetReplicationFileName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDirectory->GetLDAPURL(getter_AddRefs(mURL));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDirectory->GetAuthDn(mLogin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mConnection = do_CreateInstance(NS_LDAPCONNECTION_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+
+ return rv;
+}
+
+nsresult nsAbLDAPReplicationQuery::ConnectToLDAPServer()
+{
+ if (!mInitialized || !mURL)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsILDAPMessageListener> mDp = do_QueryInterface(mDataProcessor,
+ &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNEXPECTED;
+
+ // this could be a rebind call
+ int32_t replicationState = nsIAbLDAPProcessReplicationData::kIdle;
+ rv = mDataProcessor->GetReplicationState(&replicationState);
+ if (NS_FAILED(rv) ||
+ replicationState != nsIAbLDAPProcessReplicationData::kIdle)
+ return rv;
+
+ uint32_t protocolVersion;
+ rv = mDirectory->GetProtocolVersion(&protocolVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // initialize the LDAP connection
+ return mConnection->Init(mURL, mLogin, mDp, nullptr, protocolVersion);
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationQuery::Init(nsIAbLDAPDirectory *aDirectory,
+ nsIWebProgressListener *aProgressListener)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ mDirectory = aDirectory;
+
+ nsresult rv = InitLDAPData();
+ if (NS_FAILED(rv))
+ return rv;
+
+ mDataProcessor =
+ do_CreateInstance(NS_ABLDAP_PROCESSREPLICATIONDATA_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // 'this' initialized
+ mInitialized = true;
+
+ return mDataProcessor->Init(mDirectory, mConnection, mURL, this,
+ aProgressListener);
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationQuery::DoReplicationQuery()
+{
+ return ConnectToLDAPServer();
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationQuery::CancelQuery()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mDataProcessor->Abort();
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationQuery::Done(bool aSuccess)
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIAbLDAPReplicationService> replicationService =
+ do_GetService(NS_ABLDAP_REPLICATIONSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ replicationService->Done(aSuccess);
+
+ return rv;
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationQuery.h b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.h
new file mode 100644
index 000000000..f5d7cdda7
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.h
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsAbLDAPReplicationQuery_h__
+#define nsAbLDAPReplicationQuery_h__
+
+#include "nsIWebProgressListener.h"
+#include "nsIAbLDAPReplicationQuery.h"
+#include "nsIAbLDAPReplicationData.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsILDAPConnection.h"
+#include "nsILDAPOperation.h"
+#include "nsILDAPURL.h"
+#include "nsDirPrefs.h"
+#include "nsStringGlue.h"
+
+class nsAbLDAPReplicationQuery final : public nsIAbLDAPReplicationQuery
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABLDAPREPLICATIONQUERY
+
+ nsAbLDAPReplicationQuery();
+
+ nsresult InitLDAPData();
+ nsresult ConnectToLDAPServer();
+
+protected :
+ ~nsAbLDAPReplicationQuery() {}
+ // pointer to interfaces used by this object
+ nsCOMPtr<nsILDAPConnection> mConnection;
+ nsCOMPtr<nsILDAPOperation> mOperation;
+ nsCOMPtr<nsILDAPURL> mURL;
+ nsCOMPtr<nsIAbLDAPDirectory> mDirectory;
+
+ nsCOMPtr<nsIAbLDAPProcessReplicationData> mDataProcessor;
+
+ bool mInitialized;
+ nsCString mLogin;
+};
+
+#endif // nsAbLDAPReplicationQuery_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationService.cpp b/mailnews/addrbook/src/nsAbLDAPReplicationService.cpp
new file mode 100644
index 000000000..0ee53d719
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationService.cpp
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsCOMPtr.h"
+#include "nsAbLDAPReplicationService.h"
+#include "nsAbLDAPReplicationQuery.h"
+#include "nsAbBaseCID.h"
+#include "nsIWebProgressListener.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+// XXX Change log replication doesn't work. Bug 311632 should fix it.
+//#include "nsAbLDAPChangeLogQuery.h"
+#include "nsIAbLDAPReplicationData.h"
+
+/*** implementation of the service ******/
+
+NS_IMPL_ISUPPORTS(nsAbLDAPReplicationService, nsIAbLDAPReplicationService)
+
+nsAbLDAPReplicationService::nsAbLDAPReplicationService()
+ : mReplicating(false)
+{
+}
+
+nsAbLDAPReplicationService::~nsAbLDAPReplicationService()
+{
+}
+
+/* void startReplication(in string aURI, in nsIWebProgressListener progressListener); */
+NS_IMETHODIMP nsAbLDAPReplicationService::StartReplication(nsIAbLDAPDirectory *aDirectory,
+ nsIWebProgressListener *progressListener)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+#ifdef DEBUG_rdayal
+ printf("Start Replication called");
+#endif
+
+ // Makes sure to allow only one replication at a time.
+ if(mReplicating)
+ return NS_ERROR_FAILURE;
+
+ mDirectory = aDirectory;
+
+ nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
+
+ switch (DecideProtocol())
+ {
+ case nsIAbLDAPProcessReplicationData::kDefaultDownloadAll:
+ mQuery = do_CreateInstance(NS_ABLDAP_REPLICATIONQUERY_CONTRACTID, &rv);
+ break;
+// XXX Change log replication doesn't work. Bug 311632 should fix it.
+//case nsIAbLDAPProcessReplicationData::kChangeLogProtocol:
+// mQuery = do_CreateInstance (NS_ABLDAP_CHANGELOGQUERY_CONTRACTID, &rv);
+// break;
+ default:
+ break;
+ }
+
+ if (NS_SUCCEEDED(rv) && mQuery)
+ {
+ rv = mQuery->Init(mDirectory, progressListener);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = mQuery->DoReplicationQuery();
+ if (NS_SUCCEEDED(rv))
+ {
+ mReplicating = true;
+ return rv;
+ }
+ }
+ }
+
+ if (progressListener && NS_FAILED(rv))
+ progressListener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP,
+ NS_OK);
+
+ if (NS_FAILED(rv))
+ {
+ mDirectory = nullptr;
+ mQuery = nullptr;
+ }
+
+ return rv;
+}
+
+/* void cancelReplication(in string aURI); */
+NS_IMETHODIMP nsAbLDAPReplicationService::CancelReplication(nsIAbLDAPDirectory *aDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aDirectory == mDirectory)
+ {
+ if (mQuery && mReplicating)
+ rv = mQuery->CancelQuery();
+ }
+
+ // If query has been cancelled successfully
+ if (NS_SUCCEEDED(rv))
+ Done(false);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationService::Done(bool aSuccess)
+{
+ mReplicating = false;
+ if (mQuery)
+ {
+ mQuery = nullptr; // Release query obj
+ mDirectory = nullptr; // Release directory
+ }
+
+ return NS_OK;
+}
+
+
+// XXX: This method should query the RootDSE for the changeLog attribute,
+// if it exists ChangeLog protocol is supported.
+int32_t nsAbLDAPReplicationService::DecideProtocol()
+{
+ // Do the changeLog, it will decide if there is a need to replicate all
+ // entries or only update existing DB and will do the appropriate thing.
+ //
+ // XXX: Bug 231965 changed this from kChangeLogProtocol to
+ // kDefaultDownloadAll because of a problem with ldap replication not
+ // working correctly. We need to change this back at some stage (bug 311632).
+ return nsIAbLDAPProcessReplicationData::kDefaultDownloadAll;
+}
+
+
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationService.h b/mailnews/addrbook/src/nsAbLDAPReplicationService.h
new file mode 100644
index 000000000..07fb768f7
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationService.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+
+#ifndef nsAbLDAPReplicationService_h___
+#define nsAbLDAPReplicationService_h___
+
+#include "nsIAbLDAPReplicationService.h"
+#include "nsIAbLDAPReplicationQuery.h"
+#include "nsStringGlue.h"
+
+class nsAbLDAPReplicationService : public nsIAbLDAPReplicationService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABLDAPREPLICATIONSERVICE
+
+ nsAbLDAPReplicationService();
+
+ int32_t DecideProtocol();
+
+protected:
+ virtual ~nsAbLDAPReplicationService();
+ nsCOMPtr<nsIAbLDAPReplicationQuery> mQuery;
+ bool mReplicating;
+ nsCOMPtr<nsIAbLDAPDirectory> mDirectory;
+
+};
+
+
+#endif /* nsAbLDAPReplicationService_h___ */
diff --git a/mailnews/addrbook/src/nsAbLDIFService.cpp b/mailnews/addrbook/src/nsAbLDIFService.cpp
new file mode 100644
index 000000000..95eb4b96a
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDIFService.cpp
@@ -0,0 +1,868 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsIAddrDatabase.h"
+#include "nsStringGlue.h"
+#include "nsAbLDIFService.h"
+#include "nsIFile.h"
+#include "nsILineInputStream.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsISeekableStream.h"
+#include "mdb.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "prprf.h"
+#include "nsCRTGlue.h"
+#include "nsTArray.h"
+
+#include <ctype.h>
+
+NS_IMPL_ISUPPORTS(nsAbLDIFService, nsIAbLDIFService)
+
+// If we get a line longer than 32K it's just toooooo bad!
+#define kTextAddressBufferSz (64 * 1024)
+
+nsAbLDIFService::nsAbLDIFService()
+{
+ mStoreLocAsHome = false;
+ mLFCount = 0;
+ mCRCount = 0;
+}
+
+nsAbLDIFService::~nsAbLDIFService()
+{
+}
+
+#define RIGHT2 0x03
+#define RIGHT4 0x0f
+#define CONTINUED_LINE_MARKER '\001'
+
+// XXX TODO fix me
+// use the NSPR base64 library. see plbase64.h
+// see bug #145367
+static unsigned char b642nib[0x80] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+ 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+NS_IMETHODIMP nsAbLDIFService::ImportLDIFFile(nsIAddrDatabase *aDb, nsIFile *aSrc, bool aStoreLocAsHome, uint32_t *aProgress)
+{
+ NS_ENSURE_ARG_POINTER(aSrc);
+ NS_ENSURE_ARG_POINTER(aDb);
+
+ mStoreLocAsHome = aStoreLocAsHome;
+
+ char buf[1024];
+ char* pBuf = &buf[0];
+ int32_t startPos = 0;
+ uint32_t len = 0;
+ nsTArray<int32_t> listPosArray; // where each list/group starts in ldif file
+ nsTArray<int32_t> listSizeArray; // size of the list/group info
+ int32_t savedStartPos = 0;
+ int32_t filePos = 0;
+ uint64_t bytesLeft = 0;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize the parser for a run...
+ mLdifLine.Truncate();
+
+ while (NS_SUCCEEDED(inputStream->Available(&bytesLeft)) && bytesLeft > 0)
+ {
+ if (NS_SUCCEEDED(inputStream->Read(pBuf, sizeof(buf), &len)) && len > 0)
+ {
+ startPos = 0;
+
+ while (NS_SUCCEEDED(GetLdifStringRecord(buf, len, startPos)))
+ {
+ if (mLdifLine.Find("groupOfNames") == -1)
+ AddLdifRowToDatabase(aDb, false);
+ else
+ {
+ //keep file position for mailing list
+ listPosArray.AppendElement(savedStartPos);
+ listSizeArray.AppendElement(filePos + startPos-savedStartPos);
+ ClearLdifRecordBuffer();
+ }
+ savedStartPos = filePos + startPos;
+ }
+ filePos += len;
+ if (aProgress)
+ *aProgress = (uint32_t)filePos;
+ }
+ }
+ //last row
+ if (!mLdifLine.IsEmpty() && mLdifLine.Find("groupOfNames") == -1)
+ AddLdifRowToDatabase(aDb, false);
+
+ // mail Lists
+ int32_t i, pos;
+ uint32_t size;
+ int32_t listTotal = listPosArray.Length();
+ char *listBuf;
+ ClearLdifRecordBuffer(); // make sure the buffer is clean
+
+ nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(inputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < listTotal; i++)
+ {
+ pos = listPosArray[i];
+ size = listSizeArray[i];
+ if (NS_SUCCEEDED(seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, pos)))
+ {
+ // Allocate enough space for the lists/groups as the size varies.
+ listBuf = (char *) PR_Malloc(size);
+ if (!listBuf)
+ continue;
+ if (NS_SUCCEEDED(inputStream->Read(listBuf, size, &len)) && len > 0)
+ {
+ startPos = 0;
+
+ while (NS_SUCCEEDED(GetLdifStringRecord(listBuf, len, startPos)))
+ {
+ if (mLdifLine.Find("groupOfNames") != -1)
+ {
+ AddLdifRowToDatabase(aDb, true);
+ if (NS_SUCCEEDED(seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0)))
+ break;
+ }
+ }
+ }
+ PR_FREEIF(listBuf);
+ }
+ }
+
+ rv = inputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally commit everything to the database and return.
+ return aDb->Commit(nsAddrDBCommitType::kLargeCommit);
+}
+
+/*
+ * str_parse_line - takes a line of the form "type:[:] value" and splits it
+ * into components "type" and "value". if a double colon separates type from
+ * value, then value is encoded in base 64, and parse_line un-decodes it
+ * (in place) before returning.
+ * in LDIF, non-ASCII data is treated as base64 encoded UTF-8
+ */
+
+nsresult nsAbLDIFService::str_parse_line(char *line, char **type, char **value, int *vlen) const
+{
+ char *p, *s, *d, *byte, *stop;
+ char nib;
+ int i, b64;
+
+ /* skip any leading space */
+ while ( isspace( *line ) ) {
+ line++;
+ }
+ *type = line;
+
+ for ( s = line; *s && *s != ':'; s++ )
+ ; /* NULL */
+ if ( *s == '\0' ) {
+ return NS_ERROR_FAILURE;
+ }
+
+ /* trim any space between type and : */
+ for ( p = s - 1; p > line && isspace( *p ); p-- ) {
+ *p = '\0';
+ }
+ *s++ = '\0';
+
+ /* check for double : - indicates base 64 encoded value */
+ if ( *s == ':' ) {
+ s++;
+ b64 = 1;
+ /* single : - normally encoded value */
+ } else {
+ b64 = 0;
+ }
+
+ /* skip space between : and value */
+ while ( isspace( *s ) ) {
+ s++;
+ }
+
+ /* if no value is present, error out */
+ if ( *s == '\0' ) {
+ return NS_ERROR_FAILURE;
+ }
+
+ /* check for continued line markers that should be deleted */
+ for ( p = s, d = s; *p; p++ ) {
+ if ( *p != CONTINUED_LINE_MARKER )
+ *d++ = *p;
+ }
+ *d = '\0';
+
+ *value = s;
+ if ( b64 ) {
+ stop = PL_strchr( s, '\0' );
+ byte = s;
+ for ( p = s, *vlen = 0; p < stop; p += 4, *vlen += 3 ) {
+ for ( i = 0; i < 3; i++ ) {
+ if ( p[i] != '=' && (p[i] & 0x80 ||
+ b642nib[ p[i] & 0x7f ] > 0x3f) ) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ /* first digit */
+ nib = b642nib[ p[0] & 0x7f ];
+ byte[0] = nib << 2;
+ /* second digit */
+ nib = b642nib[ p[1] & 0x7f ];
+ byte[0] |= nib >> 4;
+ byte[1] = (nib & RIGHT4) << 4;
+ /* third digit */
+ if ( p[2] == '=' ) {
+ *vlen += 1;
+ break;
+ }
+ nib = b642nib[ p[2] & 0x7f ];
+ byte[1] |= nib >> 2;
+ byte[2] = (nib & RIGHT2) << 6;
+ /* fourth digit */
+ if ( p[3] == '=' ) {
+ *vlen += 2;
+ break;
+ }
+ nib = b642nib[ p[3] & 0x7f ];
+ byte[2] |= nib;
+
+ byte += 3;
+ }
+ s[ *vlen ] = '\0';
+ } else {
+ *vlen = (int) (d - s);
+ }
+ return NS_OK;
+}
+
+/*
+ * str_getline - return the next "line" (minus newline) of input from a
+ * string buffer of lines separated by newlines, terminated by \n\n
+ * or \0. this routine handles continued lines, bundling them into
+ * a single big line before returning. if a line begins with a white
+ * space character, it is a continuation of the previous line. the white
+ * space character (nb: only one char), and preceeding newline are changed
+ * into CONTINUED_LINE_MARKER chars, to be deleted later by the
+ * str_parse_line() routine above.
+ *
+ * it takes a pointer to a pointer to the buffer on the first call,
+ * which it updates and must be supplied on subsequent calls.
+ */
+
+char* nsAbLDIFService::str_getline(char **next) const
+{
+ char *lineStr;
+ char c;
+
+ if ( *next == nullptr || **next == '\n' || **next == '\0' ) {
+ return( nullptr);
+ }
+
+ lineStr = *next;
+ while ( (*next = PL_strchr( *next, '\n' )) != NULL ) {
+ c = *(*next + 1);
+ if ( isspace( c ) && c != '\n' ) {
+ **next = CONTINUED_LINE_MARKER;
+ *(*next+1) = CONTINUED_LINE_MARKER;
+ } else {
+ *(*next)++ = '\0';
+ break;
+ }
+ }
+
+ return( lineStr );
+}
+
+nsresult nsAbLDIFService::GetLdifStringRecord(char* buf, int32_t len, int32_t& stopPos)
+{
+ for (; stopPos < len; stopPos++)
+ {
+ char c = buf[stopPos];
+
+ if (c == 0xA)
+ {
+ mLFCount++;
+ }
+ else if (c == 0xD)
+ {
+ mCRCount++;
+ }
+ else
+ {
+ if (mLFCount == 0 && mCRCount == 0)
+ mLdifLine.Append(c);
+ else if (( mLFCount > 1) || ( mCRCount > 2 && mLFCount ) ||
+ ( !mLFCount && mCRCount > 1 ))
+ {
+ return NS_OK;
+ }
+ else if ((mLFCount == 1 || mCRCount == 1))
+ {
+ mLdifLine.Append('\n');
+ mLdifLine.Append(c);
+ mLFCount = 0;
+ mCRCount = 0;
+ }
+ }
+ }
+
+ if (((stopPos == len) && (mLFCount > 1)) || (mCRCount > 2 && mLFCount) ||
+ (!mLFCount && mCRCount > 1))
+ return NS_OK;
+
+ return NS_ERROR_FAILURE;
+}
+
+void nsAbLDIFService::AddLdifRowToDatabase(nsIAddrDatabase *aDatabase,
+ bool bIsList)
+{
+ // If no data to process then reset CR/LF counters and return.
+ if (mLdifLine.IsEmpty())
+ {
+ mLFCount = 0;
+ mCRCount = 0;
+ return;
+ }
+
+ nsCOMPtr <nsIMdbRow> newRow;
+ if (aDatabase)
+ {
+ if (bIsList)
+ aDatabase->GetNewListRow(getter_AddRefs(newRow));
+ else
+ aDatabase->GetNewRow(getter_AddRefs(newRow));
+
+ if (!newRow)
+ return;
+ }
+ else
+ return;
+
+ char* cursor = ToNewCString(mLdifLine);
+ char* saveCursor = cursor; /* keep for deleting */
+ char* line = 0;
+ char* typeSlot = 0;
+ char* valueSlot = 0;
+ int length = 0; // the length of an ldif attribute
+ while ( (line = str_getline(&cursor)) != nullptr)
+ {
+ if (NS_SUCCEEDED(str_parse_line(line, &typeSlot, &valueSlot, &length))) {
+ AddLdifColToDatabase(aDatabase, newRow, typeSlot, valueSlot, bIsList);
+ }
+ else
+ continue; // parse error: continue with next loop iteration
+ }
+ free(saveCursor);
+ aDatabase->AddCardRowToDB(newRow);
+
+ if (bIsList)
+ aDatabase->AddListDirNode(newRow);
+
+ // Clear buffer for next record
+ ClearLdifRecordBuffer();
+}
+
+void nsAbLDIFService::AddLdifColToDatabase(nsIAddrDatabase *aDatabase,
+ nsIMdbRow* newRow, char* typeSlot,
+ char* valueSlot, bool bIsList)
+{
+ nsAutoCString colType(typeSlot);
+ nsAutoCString column(valueSlot);
+
+ // 4.x exports attributes like "givenname",
+ // mozilla does "givenName" to be compliant with RFC 2798
+ ToLowerCase(colType);
+
+ mdb_u1 firstByte = (mdb_u1)(colType.get())[0];
+ switch ( firstByte )
+ {
+ case 'b':
+ if (colType.EqualsLiteral("birthyear"))
+ aDatabase->AddBirthYear(newRow, column.get());
+ else if (colType.EqualsLiteral("birthmonth"))
+ aDatabase->AddBirthMonth(newRow, column.get());
+ else if (colType.EqualsLiteral("birthday"))
+ aDatabase->AddBirthDay(newRow, column.get());
+ break; // 'b'
+
+ case 'c':
+ if (colType.EqualsLiteral("cn") || colType.EqualsLiteral("commonname"))
+ {
+ if (bIsList)
+ aDatabase->AddListName(newRow, column.get());
+ else
+ aDatabase->AddDisplayName(newRow, column.get());
+ }
+ else if (colType.EqualsLiteral("c") || colType.EqualsLiteral("countryname"))
+ {
+ if (mStoreLocAsHome )
+ aDatabase->AddHomeCountry(newRow, column.get());
+ else
+ aDatabase->AddWorkCountry(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("cellphone") )
+ aDatabase->AddCellularNumber(newRow, column.get());
+
+ else if (colType.EqualsLiteral("carphone"))
+ aDatabase->AddCellularNumber(newRow, column.get());
+
+ else if (colType.EqualsLiteral("custom1"))
+ aDatabase->AddCustom1(newRow, column.get());
+
+ else if (colType.EqualsLiteral("custom2"))
+ aDatabase->AddCustom2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("custom3"))
+ aDatabase->AddCustom3(newRow, column.get());
+
+ else if (colType.EqualsLiteral("custom4"))
+ aDatabase->AddCustom4(newRow, column.get());
+
+ else if (colType.EqualsLiteral("company"))
+ aDatabase->AddCompany(newRow, column.get());
+ break; // 'c'
+
+ case 'd':
+ if (colType.EqualsLiteral("description"))
+ {
+ if (bIsList)
+ aDatabase->AddListDescription(newRow, column.get());
+ else
+ aDatabase->AddNotes(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("department"))
+ aDatabase->AddDepartment(newRow, column.get());
+
+ else if (colType.EqualsLiteral("displayname"))
+ {
+ if (bIsList)
+ aDatabase->AddListName(newRow, column.get());
+ else
+ aDatabase->AddDisplayName(newRow, column.get());
+ }
+ break; // 'd'
+
+ case 'f':
+
+ if (colType.EqualsLiteral("fax") ||
+ colType.EqualsLiteral("facsimiletelephonenumber"))
+ aDatabase->AddFaxNumber(newRow, column.get());
+ break; // 'f'
+
+ case 'g':
+ if (colType.EqualsLiteral("givenname"))
+ aDatabase->AddFirstName(newRow, column.get());
+
+ break; // 'g'
+
+ case 'h':
+ if (colType.EqualsLiteral("homephone"))
+ aDatabase->AddHomePhone(newRow, column.get());
+
+ else if (colType.EqualsLiteral("homestreet"))
+ aDatabase->AddHomeAddress(newRow, column.get());
+
+ else if (colType.EqualsLiteral("homeurl"))
+ aDatabase->AddWebPage2(newRow, column.get());
+ break; // 'h'
+
+ case 'l':
+ if (colType.EqualsLiteral("l") || colType.EqualsLiteral("locality"))
+ {
+ if (mStoreLocAsHome)
+ aDatabase->AddHomeCity(newRow, column.get());
+ else
+ aDatabase->AddWorkCity(newRow, column.get());
+ }
+ // labeledURI contains a URI and, optionally, a label
+ // This will remove the label and place the URI as the work URL
+ else if (colType.EqualsLiteral("labeleduri"))
+ {
+ int32_t index = column.FindChar(' ');
+ if (index != -1)
+ column.SetLength(index);
+
+ aDatabase->AddWebPage1(newRow, column.get());
+ }
+
+ break; // 'l'
+
+ case 'm':
+ if (colType.EqualsLiteral("mail"))
+ aDatabase->AddPrimaryEmail(newRow, column.get());
+
+ else if (colType.EqualsLiteral("member") && bIsList)
+ aDatabase->AddLdifListMember(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mobile"))
+ aDatabase->AddCellularNumber(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozilla_aimscreenname"))
+ aDatabase->AddAimScreenName(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillacustom1"))
+ aDatabase->AddCustom1(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillacustom2"))
+ aDatabase->AddCustom2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillacustom3"))
+ aDatabase->AddCustom3(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillacustom4"))
+ aDatabase->AddCustom4(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomecountryname"))
+ aDatabase->AddHomeCountry(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomelocalityname"))
+ aDatabase->AddHomeCity(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomestate"))
+ aDatabase->AddHomeState(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomestreet"))
+ aDatabase->AddHomeAddress(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomestreet2"))
+ aDatabase->AddHomeAddress2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomepostalcode"))
+ aDatabase->AddHomeZipCode(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomeurl"))
+ aDatabase->AddWebPage2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillanickname"))
+ {
+ if (bIsList)
+ aDatabase->AddListNickName(newRow, column.get());
+ else
+ aDatabase->AddNickName(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("mozillasecondemail"))
+ aDatabase->Add2ndEmail(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillausehtmlmail"))
+ {
+ ToLowerCase(column);
+ if (-1 != column.Find("true"))
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::html);
+ else if (-1 != column.Find("false"))
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::plaintext);
+ else
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::unknown);
+ }
+
+ else if (colType.EqualsLiteral("mozillaworkstreet2"))
+ aDatabase->AddWorkAddress2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillaworkurl"))
+ aDatabase->AddWebPage1(newRow, column.get());
+
+ break; // 'm'
+
+ case 'n':
+ if (colType.EqualsLiteral("notes"))
+ aDatabase->AddNotes(newRow, column.get());
+
+ else if (colType.EqualsLiteral("nscpaimscreenname") ||
+ colType.EqualsLiteral("nsaimid"))
+ aDatabase->AddAimScreenName(newRow, column.get());
+
+ break; // 'n'
+
+ case 'o':
+ if (colType.EqualsLiteral("objectclass"))
+ break;
+
+ else if (colType.EqualsLiteral("ou") || colType.EqualsLiteral("orgunit"))
+ aDatabase->AddDepartment(newRow, column.get());
+
+ else if (colType.EqualsLiteral("o")) // organization
+ aDatabase->AddCompany(newRow, column.get());
+
+ break; // 'o'
+
+ case 'p':
+ if (colType.EqualsLiteral("postalcode"))
+ {
+ if (mStoreLocAsHome)
+ aDatabase->AddHomeZipCode(newRow, column.get());
+ else
+ aDatabase->AddWorkZipCode(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("postofficebox"))
+ {
+ nsAutoCString workAddr1, workAddr2;
+ SplitCRLFAddressField(column, workAddr1, workAddr2);
+ aDatabase->AddWorkAddress(newRow, workAddr1.get());
+ aDatabase->AddWorkAddress2(newRow, workAddr2.get());
+ }
+ else if (colType.EqualsLiteral("pager") || colType.EqualsLiteral("pagerphone"))
+ aDatabase->AddPagerNumber(newRow, column.get());
+
+ break; // 'p'
+
+ case 'r':
+ if (colType.EqualsLiteral("region"))
+ {
+ aDatabase->AddWorkState(newRow, column.get());
+ }
+
+ break; // 'r'
+
+ case 's':
+ if (colType.EqualsLiteral("sn") || colType.EqualsLiteral("surname"))
+ aDatabase->AddLastName(newRow, column.get());
+
+ else if (colType.EqualsLiteral("street"))
+ aDatabase->AddWorkAddress(newRow, column.get());
+
+ else if (colType.EqualsLiteral("streetaddress"))
+ {
+ nsAutoCString addr1, addr2;
+ SplitCRLFAddressField(column, addr1, addr2);
+ if (mStoreLocAsHome)
+ {
+ aDatabase->AddHomeAddress(newRow, addr1.get());
+ aDatabase->AddHomeAddress2(newRow, addr2.get());
+ }
+ else
+ {
+ aDatabase->AddWorkAddress(newRow, addr1.get());
+ aDatabase->AddWorkAddress2(newRow, addr2.get());
+ }
+ }
+ else if (colType.EqualsLiteral("st"))
+ {
+ if (mStoreLocAsHome)
+ aDatabase->AddHomeState(newRow, column.get());
+ else
+ aDatabase->AddWorkState(newRow, column.get());
+ }
+
+ break; // 's'
+
+ case 't':
+ if (colType.EqualsLiteral("title"))
+ aDatabase->AddJobTitle(newRow, column.get());
+
+ else if (colType.EqualsLiteral("telephonenumber") )
+ {
+ aDatabase->AddWorkPhone(newRow, column.get());
+ }
+
+ break; // 't'
+
+ case 'u':
+
+ if (colType.EqualsLiteral("uniquemember") && bIsList)
+ aDatabase->AddLdifListMember(newRow, column.get());
+
+ break; // 'u'
+
+ case 'w':
+ if (colType.EqualsLiteral("workurl"))
+ aDatabase->AddWebPage1(newRow, column.get());
+
+ break; // 'w'
+
+ case 'x':
+ if (colType.EqualsLiteral("xmozillanickname"))
+ {
+ if (bIsList)
+ aDatabase->AddListNickName(newRow, column.get());
+ else
+ aDatabase->AddNickName(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("xmozillausehtmlmail"))
+ {
+ ToLowerCase(column);
+ if (-1 != column.Find("true"))
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::html);
+ else if (-1 != column.Find("false"))
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::plaintext);
+ else
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::unknown);
+ }
+
+ break; // 'x'
+
+ case 'z':
+ if (colType.EqualsLiteral("zip")) // alias for postalcode
+ {
+ if (mStoreLocAsHome)
+ aDatabase->AddHomeZipCode(newRow, column.get());
+ else
+ aDatabase->AddWorkZipCode(newRow, column.get());
+ }
+
+ break; // 'z'
+
+ default:
+ break; // default
+ }
+}
+
+void nsAbLDIFService::ClearLdifRecordBuffer()
+{
+ if (!mLdifLine.IsEmpty())
+ {
+ mLdifLine.Truncate();
+ mLFCount = 0;
+ mCRCount = 0;
+ }
+}
+
+// Some common ldif fields, it an ldif file has NONE of these entries
+// then it is most likely NOT an ldif file!
+static const char *const sLDIFFields[] = {
+ "objectclass",
+ "sn",
+ "dn",
+ "cn",
+ "givenName",
+ "mail",
+ nullptr
+};
+#define kMaxLDIFLen 14
+
+// Count total number of legal ldif fields and records in the first 100 lines of the
+// file and if the average legal ldif field is 3 or higher than it's a valid ldif file.
+NS_IMETHODIMP nsAbLDIFService::IsLDIFFile(nsIFile *pSrc, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(pSrc);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = false;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), pSrc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t lineLen = 0;
+ int32_t lineCount = 0;
+ int32_t ldifFields = 0; // total number of legal ldif fields.
+ char field[kMaxLDIFLen];
+ int32_t fLen = 0;
+ const char *pChar;
+ int32_t recCount = 0; // total number of records.
+ int32_t i;
+ bool gotLDIF = false;
+ bool more = true;
+ nsCString line;
+
+ while (more && NS_SUCCEEDED(rv) && (lineCount < 100))
+ {
+ rv = lineInputStream->ReadLine(line, &more);
+
+ if (NS_SUCCEEDED(rv) && more)
+ {
+ pChar = line.get();
+ lineLen = line.Length();
+ if (!lineLen && gotLDIF)
+ {
+ recCount++;
+ gotLDIF = false;
+ }
+
+ if (lineLen && (*pChar != ' ') && (*pChar != '\t'))
+ {
+ fLen = 0;
+
+ while (lineLen && (fLen < (kMaxLDIFLen - 1)) && (*pChar != ':'))
+ {
+ field[fLen] = *pChar;
+ pChar++;
+ fLen++;
+ lineLen--;
+ }
+
+ field[fLen] = 0;
+
+ if (lineLen && (*pChar == ':') && (fLen < (kMaxLDIFLen - 1)))
+ {
+ // see if this is an ldif field (case insensitive)?
+ i = 0;
+ while (sLDIFFields[i])
+ {
+ if (!PL_strcasecmp( sLDIFFields[i], field))
+ {
+ ldifFields++;
+ gotLDIF = true;
+ break;
+ }
+ i++;
+ }
+ }
+ }
+ }
+ lineCount++;
+ }
+
+ // If we just saw ldif address, increment recCount.
+ if (gotLDIF)
+ recCount++;
+
+ rv = fileStream->Close();
+
+ if (recCount > 1)
+ ldifFields /= recCount;
+
+ // If the average field number >= 3 then it's a good ldif file.
+ if (ldifFields >= 3)
+ {
+ *_retval = true;
+ }
+
+ return rv;
+}
+
+void nsAbLDIFService::SplitCRLFAddressField(nsCString &inputAddress, nsCString &outputLine1, nsCString &outputLine2) const
+{
+ int32_t crlfPos = inputAddress.Find("\r\n");
+ if (crlfPos != -1)
+ {
+ outputLine1 = Substring(inputAddress, 0, crlfPos);
+ outputLine2 = Substring(inputAddress, crlfPos + 2);
+ }
+ else
+ outputLine1.Assign(inputAddress);
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDIFService.h b/mailnews/addrbook/src/nsAbLDIFService.h
new file mode 100644
index 000000000..8f50559c9
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDIFService.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __nsAbLDIFService_h
+#define __nsAbLDIFService_h
+
+#include "nsIAbLDIFService.h"
+#include "nsCOMPtr.h"
+
+class nsIMdbRow;
+
+class nsAbLDIFService : public nsIAbLDIFService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABLDIFSERVICE
+
+ nsAbLDIFService();
+private:
+ virtual ~nsAbLDIFService();
+ nsresult str_parse_line(char *line, char **type, char **value, int *vlen) const;
+ char * str_getline(char **next) const;
+ nsresult GetLdifStringRecord(char* buf, int32_t len, int32_t& stopPos);
+ void AddLdifRowToDatabase(nsIAddrDatabase *aDatabase, bool aIsList);
+ void AddLdifColToDatabase(nsIAddrDatabase *aDatabase, nsIMdbRow* newRow,
+ char* typeSlot, char* valueSlot, bool bIsList);
+ void ClearLdifRecordBuffer();
+ void SplitCRLFAddressField(nsCString &inputAddress, nsCString &outputLine1, nsCString &outputLine2) const;
+
+ bool mStoreLocAsHome;
+ nsCString mLdifLine;
+ int32_t mLFCount;
+ int32_t mCRCount;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbMDBCard.cpp b/mailnews/addrbook/src/nsAbMDBCard.cpp
new file mode 100644
index 000000000..14af6a875
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBCard.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbMDBCard.h"
+
+nsAbMDBCard::nsAbMDBCard(void)
+{
+}
+
+nsAbMDBCard::~nsAbMDBCard(void)
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsAbMDBCard, nsAbCardProperty)
+
+NS_IMETHODIMP nsAbMDBCard::Equals(nsIAbCard *card, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(card);
+ NS_ENSURE_ARG_POINTER(result);
+
+ if (this == card) {
+ *result = true;
+ return NS_OK;
+ }
+
+ // If we have the same directory, we will equal the other card merely given
+ // the row IDs. If not, we are never equal. But we are dumb in that we don't
+ // know who our directory is, which may change in the future. For now,
+ // however, the only known users of this method are for locating us in a list
+ // of cards, most commonly mailing lists; a warning on the IDL has also
+ // notified consumers that this method is not generally safe to use. In this
+ // respect, it is safe to assume that the directory portion is satisfied when
+ // making this call.
+ // However, if we make the wrong assumption, one of two things will happen.
+ // If the other directory is a local address book, we could return a spurious
+ // true result. If not, then DbRowID should be unset and we can definitively
+ // return false.
+
+ uint32_t row;
+ nsresult rv = card->GetPropertyAsUint32("DbRowID", &row);
+ if (NS_FAILED(rv))
+ {
+ *result = false;
+ return NS_OK;
+ }
+
+ uint32_t ourRow;
+ rv = GetPropertyAsUint32("DbRowID", &ourRow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = (row == ourRow);
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbMDBCard.h b/mailnews/addrbook/src/nsAbMDBCard.h
new file mode 100644
index 000000000..0d9123bc4
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBCard.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbMDBCard_h__
+#define nsAbMDBCard_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbCardProperty.h"
+#include "nsCOMPtr.h"
+
+class nsAbMDBCard: public nsAbCardProperty
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsAbMDBCard(void);
+
+ NS_IMETHOD Equals(nsIAbCard *card, bool *result) override;
+
+private:
+ virtual ~nsAbMDBCard();
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbMDBDirFactory.cpp b/mailnews/addrbook/src/nsAbMDBDirFactory.cpp
new file mode 100644
index 000000000..73ecb32fc
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirFactory.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbMDBDirFactory.h"
+#include "nsAbUtils.h"
+#include "nsStringGlue.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#include "nsIAbManager.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsAbMDBDirFactory.h"
+#include "nsIAddrDBListener.h"
+#include "nsIAddrDatabase.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsAbBaseCID.h"
+
+NS_IMPL_ISUPPORTS(nsAbMDBDirFactory, nsIAbDirFactory)
+
+nsAbMDBDirFactory::nsAbMDBDirFactory()
+{
+}
+
+nsAbMDBDirFactory::~nsAbMDBDirFactory()
+{
+}
+
+NS_IMETHODIMP nsAbMDBDirFactory::GetDirectories(const nsAString &aDirName,
+ const nsACString &aURI,
+ const nsACString &aPrefName,
+ nsISimpleEnumerator **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(aURI, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->SetDirPrefId(aPrefName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dbPath;
+ rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
+
+ nsCOMPtr<nsIAddrDatabase> listDatabase;
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString fileName;
+
+ if (StringBeginsWith(aURI, NS_LITERAL_CSTRING(kMDBDirectoryRoot)))
+ fileName = Substring(aURI, kMDBDirectoryRootLen, aURI.Length() - kMDBDirectoryRootLen);
+
+ rv = dbPath->AppendNative(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory = do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = addrDBFactory->Open(dbPath, true, true, getter_AddRefs(listDatabase));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = listDatabase->GetMailingListsFromDB(directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewSingletonEnumerator(_retval, directory);
+}
+
+/* void deleteDirectory (in nsIAbDirectory directory); */
+NS_IMETHODIMP nsAbMDBDirFactory::DeleteDirectory(nsIAbDirectory *directory)
+{
+ if (!directory)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ rv = directory->GetAddressLists(getter_AddRefs(pAddressLists));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t total;
+ rv = pAddressLists->GetLength(&total);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < total; i++)
+ {
+ nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(pAddressLists, i, &rv));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIAbMDBDirectory> dblistDir(do_QueryInterface(listDir, &rv));
+ if (NS_FAILED(rv))
+ break;
+
+ rv = directory->DeleteDirectory(listDir);
+ if (NS_FAILED(rv))
+ break;
+
+ rv = dblistDir->RemoveElementsFromAddressList();
+ if (NS_FAILED(rv))
+ break;
+ }
+ pAddressLists->Clear();
+
+ nsCOMPtr<nsIAbMDBDirectory> dbdirectory(do_QueryInterface(directory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return dbdirectory->ClearDatabase();
+}
+
diff --git a/mailnews/addrbook/src/nsAbMDBDirFactory.h b/mailnews/addrbook/src/nsAbMDBDirFactory.h
new file mode 100644
index 000000000..200175f62
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirFactory.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbMDBDirFactory_h__
+#define nsAbMDBDirFactory_h__
+
+#include "nsIAbDirFactory.h"
+
+class nsAbMDBDirFactory : public nsIAbDirFactory
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORY
+
+ nsAbMDBDirFactory();
+
+private:
+ virtual ~nsAbMDBDirFactory();
+};
+
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbMDBDirProperty.cpp b/mailnews/addrbook/src/nsAbMDBDirProperty.cpp
new file mode 100644
index 000000000..7df904d87
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirProperty.cpp
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbMDBDirProperty.h"
+#include "nsIServiceManager.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+#include "nsAbBaseCID.h"
+#include "nsAddrDatabase.h"
+#include "nsIAbCard.h"
+#include "nsIAbListener.h"
+#include "nsArrayUtils.h"
+#include "mdb.h"
+#include "nsComponentManagerUtils.h"
+
+nsAbMDBDirProperty::nsAbMDBDirProperty(void)
+ : nsAbDirProperty()
+{
+ m_dbRowID = 0;
+}
+
+nsAbMDBDirProperty::~nsAbMDBDirProperty(void)
+{
+}
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbMDBDirProperty, nsAbDirProperty,
+ nsIAbDirectory,
+ nsISupportsWeakReference, nsIAbMDBDirectory)
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+
+// nsIAbMDBDirectory attributes
+
+NS_IMETHODIMP nsAbMDBDirProperty::GetDbRowID(uint32_t *aDbRowID)
+{
+ *aDbRowID = m_dbRowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirProperty::SetDbRowID(uint32_t aDbRowID)
+{
+ m_dbRowID = aDbRowID;
+ return NS_OK;
+}
+
+
+// nsIAbMDBDirectory methods
+
+/* add mailing list to the parent directory */
+NS_IMETHODIMP nsAbMDBDirProperty::AddMailListToDirectory(nsIAbDirectory *mailList)
+{
+ if (!m_AddressList)
+ {
+ nsresult rv;
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t position;
+ if (NS_FAILED(m_AddressList->IndexOf(0, mailList, &position)))
+ m_AddressList->AppendElement(mailList, false);
+
+ return NS_OK;
+}
+
+/* add addresses to the mailing list */
+NS_IMETHODIMP nsAbMDBDirProperty::AddAddressToList(nsIAbCard *card)
+{
+ if (!m_AddressList)
+ {
+ nsresult rv;
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t position;
+ if (NS_FAILED(m_AddressList->IndexOf(0, card, &position)))
+ m_AddressList->AppendElement(card, false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirProperty::CopyDBMailList(nsIAbMDBDirectory* srcListDB)
+{
+ nsresult err = NS_OK;
+ nsCOMPtr<nsIAbDirectory> srcList(do_QueryInterface(srcListDB));
+ if (NS_FAILED(err))
+ return NS_ERROR_NULL_POINTER;
+
+ CopyMailList (srcList);
+
+ uint32_t rowID;
+ srcListDB->GetDbRowID(&rowID);
+ SetDbRowID(rowID);
+
+ return NS_OK;
+}
+
+
+// nsIAbMDBDirectory NOT IMPLEMENTED methods
+
+/* nsIAbDirectory addDirectory (in string uriName); */
+NS_IMETHODIMP nsAbMDBDirProperty::AddDirectory(const char *uriName, nsIAbDirectory **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void removeElementsFromAddressList (); */
+NS_IMETHODIMP nsAbMDBDirProperty::RemoveElementsFromAddressList()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void removeEmailAddressAt (in unsigned long aIndex); */
+NS_IMETHODIMP nsAbMDBDirProperty::RemoveEmailAddressAt(uint32_t aIndex)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void notifyDirItemAdded (in nsISupports item); */
+NS_IMETHODIMP nsAbMDBDirProperty::NotifyDirItemAdded(nsISupports *item)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void clearDatabase (); */
+NS_IMETHODIMP nsAbMDBDirProperty::ClearDatabase()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbMDBDirProperty::GetDatabaseFile(nsIFile **aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbMDBDirProperty::GetDatabase(nsIAddrDatabase **aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/mailnews/addrbook/src/nsAbMDBDirProperty.h b/mailnews/addrbook/src/nsAbMDBDirProperty.h
new file mode 100644
index 000000000..3ee90d17e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirProperty.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Directory
+
+*********************************************************************************************************/
+
+#ifndef nsAbMDBDirProperty_h__
+#define nsAbMDBDirProperty_h__
+
+#include "nsIAbMDBDirectory.h"
+#include "nsAbDirProperty.h"
+#include "nsIAbCard.h"
+#include "nsCOMPtr.h"
+#include "nsDirPrefs.h"
+#include "nsIAddrDatabase.h"
+
+ /*
+ * Address Book Directory
+ */
+
+class nsAbMDBDirProperty: public nsIAbMDBDirectory, public nsAbDirProperty
+{
+public:
+ nsAbMDBDirProperty(void);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABMDBDIRECTORY
+
+protected:
+ virtual ~nsAbMDBDirProperty();
+
+ uint32_t m_dbRowID;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbMDBDirectory.cpp b/mailnews/addrbook/src/nsAbMDBDirectory.cpp
new file mode 100644
index 000000000..be4799cf1
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirectory.cpp
@@ -0,0 +1,1125 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbMDBDirectory.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+#include "nsAbBaseCID.h"
+#include "nsAddrDatabase.h"
+#include "nsIAbListener.h"
+#include "nsIAbManager.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsAbDirectoryQuery.h"
+#include "nsIAbDirectoryQueryProxy.h"
+#include "nsAbQueryStringToExpression.h"
+#include "nsIMutableArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsEnumeratorUtils.h"
+#include "mdb.h"
+#include "prprf.h"
+#include "nsIPrefService.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsArrayUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/DebugOnly.h"
+
+nsAbMDBDirectory::nsAbMDBDirectory(void):
+ nsAbMDBDirProperty(),
+ mPerformingQuery(false)
+{
+}
+
+nsAbMDBDirectory::~nsAbMDBDirectory(void)
+{
+ if (mDatabase) {
+ mDatabase->RemoveListener(this);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbMDBDirectory, nsAbMDBDirProperty,
+ nsIAbDirSearchListener,
+ nsIAbDirectorySearch,
+ nsIAddrDBListener)
+
+NS_IMETHODIMP nsAbMDBDirectory::Init(const char *aUri)
+{
+ // We need to ensure that the m_DirPrefId is initialized properly
+ nsDependentCString uri(aUri);
+
+ // Find the first ? (of the search params) if there is one.
+ // We know we can start at the end of the moz-abmdbdirectory:// because
+ // that's the URI we should have been passed.
+ int32_t searchCharLocation = uri.FindChar('?', kMDBDirectoryRootLen);
+ nsAutoCString URINoQuery;
+ if (searchCharLocation != kNotFound)
+ {
+ URINoQuery = Substring(uri, 0, searchCharLocation);
+ } else {
+ URINoQuery.Assign(uri);
+ }
+
+ // In the non-query part of the URI, check if we are a mailinglist
+ if (URINoQuery.Find("MailList") != kNotFound)
+ m_IsMailList = true;
+
+ // Mailing lists don't have their own prefs.
+ if (m_DirPrefId.IsEmpty() && !m_IsMailList)
+ {
+ nsAutoCString filename;
+
+ // Extract the filename from the uri.
+ filename = Substring(URINoQuery, kMDBDirectoryRootLen);
+
+ // Get the pref servers and the address book directory branch
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefService->GetBranch(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME ".").get(),
+ getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char** childArray;
+ uint32_t childCount, i;
+ int32_t dotOffset;
+ nsCString childValue;
+ nsDependentCString child;
+
+ rv = prefBranch->GetChildList("", &childCount, &childArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < childCount; ++i)
+ {
+ child.Assign(childArray[i]);
+
+ if (StringEndsWith(child, NS_LITERAL_CSTRING(".filename")))
+ {
+ if (NS_SUCCEEDED(prefBranch->GetCharPref(child.get(),
+ getter_Copies(childValue))))
+ {
+ if (childValue == filename)
+ {
+ dotOffset = child.RFindChar('.');
+ if (dotOffset != -1)
+ {
+ nsAutoCString prefName(StringHead(child, dotOffset));
+ m_DirPrefId.AssignLiteral(PREF_LDAP_SERVER_TREE_NAME ".");
+ m_DirPrefId.Append(prefName);
+ }
+ }
+ }
+ }
+ }
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(childCount, childArray);
+
+ NS_ASSERTION(!m_DirPrefId.IsEmpty(),
+ "Error, Could not set m_DirPrefId in nsAbMDBDirectory::Init");
+ }
+
+ return nsAbDirProperty::Init(aUri);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsAbMDBDirectory::RemoveCardFromAddressList(nsIAbCard* card)
+{
+ nsresult rv = NS_OK;
+ uint32_t listTotal;
+ int32_t i, j;
+
+ // These checks ensure we don't run into null pointers
+ // as we did when we caused bug 280463.
+ if (!mDatabase)
+ {
+ rv = GetAbDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!m_AddressList)
+ {
+ rv = mDatabase->GetMailingListsFromDB(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the previous call didn't gives us an m_AddressList (and succeeded)
+ // then we haven't got any mailing lists to try and remove the card from.
+ // So just return without doing anything
+ if (!m_AddressList)
+ return NS_OK;
+ }
+
+ rv = m_AddressList->GetLength(&listTotal);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ for (i = listTotal - 1; i >= 0; i--)
+ {
+ nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(m_AddressList, i, &rv));
+ if (listDir)
+ {
+ // First remove the instance in the database
+ mDatabase->DeleteCardFromMailList(listDir, card, false);
+
+ // Now remove the instance in any lists we hold.
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ listDir->GetAddressLists(getter_AddRefs(pAddressLists));
+ if (pAddressLists)
+ {
+ uint32_t total;
+ rv = pAddressLists->GetLength(&total);
+ for (j = total - 1; j >= 0; j--)
+ {
+ nsCOMPtr<nsIAbCard> cardInList(do_QueryElementAt(pAddressLists, j, &rv));
+ bool equals;
+ rv = cardInList->Equals(card, &equals); // should we checking email?
+ if (NS_SUCCEEDED(rv) && equals)
+ pAddressLists->RemoveElementAt(j);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::DeleteDirectory(nsIAbDirectory *directory)
+{
+ NS_ENSURE_ARG_POINTER(directory);
+
+ nsCOMPtr<nsIAddrDatabase> database;
+ nsresult rv = GetDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = database->DeleteMailList(directory, this);
+
+ if (NS_SUCCEEDED(rv))
+ database->Commit(nsAddrDBCommitType::kLargeCommit);
+
+ uint32_t dirIndex;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, directory, &dirIndex)))
+ m_AddressList->RemoveElementAt(dirIndex);
+ // XXX Cast from bool to nsresult
+ rv = static_cast<nsresult>(mSubDirectories.RemoveObject(directory));
+
+ NotifyItemDeleted(directory);
+ return rv;
+}
+
+nsresult nsAbMDBDirectory::NotifyItemChanged(nsISupports *item)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = abManager->NotifyItemPropertyChanged(item, nullptr, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsAbMDBDirectory::NotifyPropertyChanged(nsIAbDirectory *list, const char *property, const char16_t* oldValue, const char16_t* newValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> supports = do_QueryInterface(list, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = abManager->NotifyItemPropertyChanged(supports, property, oldValue, newValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsAbMDBDirectory::NotifyItemAdded(nsISupports *item)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if(NS_SUCCEEDED(rv))
+ abManager->NotifyDirectoryItemAdded(this, item);
+ return NS_OK;
+}
+
+nsresult nsAbMDBDirectory::NotifyItemDeleted(nsISupports *item)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if(NS_SUCCEEDED(rv))
+ abManager->NotifyDirectoryItemDeleted(this, item);
+
+ return NS_OK;
+}
+
+// nsIAbMDBDirectory methods
+
+NS_IMETHODIMP nsAbMDBDirectory::ClearDatabase()
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (mDatabase)
+ {
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::RemoveElementsFromAddressList()
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (m_AddressList)
+ {
+ uint32_t count;
+ mozilla::DebugOnly<nsresult> rv = m_AddressList->GetLength(&count);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Count failed");
+ int32_t i;
+ for (i = count - 1; i >= 0; i--)
+ m_AddressList->RemoveElementAt(i);
+ }
+ m_AddressList = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::RemoveEmailAddressAt(uint32_t aIndex)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (m_AddressList)
+ {
+ return m_AddressList->RemoveElementAt(aIndex);
+ }
+ else
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::AddDirectory(const char *uriName, nsIAbDirectory **childDir)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (!childDir || !uriName)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(nsDependentCString(uriName), getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mSubDirectories.IndexOf(directory) == -1)
+ mSubDirectories.AppendObject(directory);
+ NS_IF_ADDREF(*childDir = directory);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetDatabaseFile(nsIFile **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCString fileName;
+ nsresult rv = GetStringValue("filename", EmptyCString(), fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fileName.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIFile> dbFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dbFile->AppendNative(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = dbFile);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetDatabase(nsIAddrDatabase **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> databaseFile;
+ rv = GetDatabaseFile(getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory =
+ do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return addrDBFactory->Open(databaseFile, false /* no create */, true,
+ aResult);
+}
+
+// nsIAbDirectory methods
+
+NS_IMETHODIMP nsAbMDBDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetChildNodes(nsISimpleEnumerator* *aResult)
+{
+ if (mIsQueryURI)
+ return NS_NewEmptyEnumerator(aResult);
+
+ return NS_NewArrayEnumerator(aResult, mSubDirectories);
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetChildCards(nsISimpleEnumerator* *result)
+{
+ nsresult rv;
+
+ if (mIsQueryURI)
+ {
+ rv = StartSearch();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO
+ // Search is synchronous so need to return
+ // results after search is complete
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ for (auto iter = mSearchCache.Iter(); !iter.Done(); iter.Next()) {
+ array->AppendElement(iter.Data(), false);
+ }
+ return NS_NewArrayEnumerator(result, array);
+ }
+
+ rv = GetAbDatabase();
+
+ if (NS_FAILED(rv) || !mDatabase)
+ return rv;
+
+ return m_IsMailList ? mDatabase->EnumerateListAddresses(this, result) :
+ mDatabase->EnumerateCards(this, result);
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mIsQueryURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::DeleteCards(nsIArray *aCards)
+{
+ NS_ENSURE_ARG_POINTER(aCards);
+ nsresult rv = NS_OK;
+
+ if (mIsQueryURI) {
+ // if this is a query, delete the cards from the directory (without the query)
+ // before we do the delete, make this directory (which represents the search)
+ // a listener on the database, so that it will get notified when the cards are deleted
+ // after delete, remove this query as a listener.
+ nsCOMPtr<nsIAddrDatabase> database;
+ rv = GetDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = database->AddListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->DeleteCards(aCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = database->RemoveListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+ }
+
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if (NS_SUCCEEDED(rv) && mDatabase)
+ {
+ uint32_t cardCount;
+ uint32_t i;
+ rv = aCards->GetLength(&cardCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (i = 0; i < cardCount; i++)
+ {
+ nsCOMPtr<nsIAbCard> card(do_QueryElementAt(aCards, i, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (card)
+ {
+ uint32_t rowID;
+ rv = card->GetPropertyAsUint32("DbRowID", &rowID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_IsMailList)
+ {
+ mDatabase->DeleteCardFromMailList(this, card, true);
+
+ uint32_t cardTotal = 0;
+ int32_t i;
+ if (m_AddressList)
+ rv = m_AddressList->GetLength(&cardTotal);
+ for (i = cardTotal - 1; i >= 0; i--)
+ {
+ nsCOMPtr<nsIAbCard> arrayCard(do_QueryElementAt(m_AddressList, i, &rv));
+ if (arrayCard)
+ {
+ // No card can have a row ID of 0
+ uint32_t arrayRowID = 0;
+ arrayCard->GetPropertyAsUint32("DbRowID", &arrayRowID);
+ if (rowID == arrayRowID)
+ m_AddressList->RemoveElementAt(i);
+ }
+ }
+ }
+ else
+ {
+ mDatabase->DeleteCard(card, true, this);
+ bool bIsMailList = false;
+ card->GetIsMailList(&bIsMailList);
+ if (bIsMailList)
+ {
+ //to do, get mailing list dir side uri and notify nsIAbManager to remove it
+ nsAutoCString listUri(mURI);
+ listUri.AppendLiteral("/MailList");
+ listUri.AppendInt(rowID);
+ if (!listUri.IsEmpty())
+ {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> listDir;
+ rv = abManager->GetDirectory(listUri, getter_AddRefs(listDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t dirIndex;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, listDir, &dirIndex)))
+ m_AddressList->RemoveElementAt(dirIndex);
+
+ mSubDirectories.RemoveObject(listDir);
+
+ if (listDir)
+ NotifyItemDeleted(listDir);
+ }
+ }
+ else
+ {
+ rv = RemoveCardFromAddressList(card);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ }
+ }
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::HasCard(nsIAbCard *cards, bool *hasCard)
+{
+ if(!hasCard)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mIsQueryURI)
+ {
+ *hasCard = mSearchCache.Get(cards, nullptr);
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if(NS_SUCCEEDED(rv) && mDatabase)
+ {
+ if(NS_SUCCEEDED(rv))
+ rv = mDatabase->ContainsCard(cards, hasCard);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::HasDirectory(nsIAbDirectory *dir, bool *hasDir)
+{
+ if (!hasDir)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbdir(do_QueryInterface(dir, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool bIsMailingList = false;
+ dir->GetIsMailList(&bIsMailingList);
+ if (bIsMailingList)
+ {
+ nsCOMPtr<nsIAddrDatabase> database;
+ rv = GetDatabase(getter_AddRefs(database));
+
+ if (NS_SUCCEEDED(rv))
+ rv = database->ContainsMailList(dir, hasDir);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::HasMailListWithName(const char16_t *aName, bool *aHasList)
+{
+ NS_ENSURE_ARG_POINTER(aHasList);
+
+ nsCOMPtr<nsIAddrDatabase> database;
+ nsresult rv = GetDatabase(getter_AddRefs(database));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = database->FindMailListbyUnicodeName(aName, aHasList);
+ if (NS_SUCCEEDED(rv) && *aHasList)
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::AddMailList(nsIAbDirectory *list, nsIAbDirectory **addedList)
+{
+ NS_ENSURE_ARG_POINTER(addedList);
+
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if (NS_FAILED(rv) || !mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list, &rv));
+ if (NS_FAILED(rv))
+ {
+ nsCOMPtr<nsIAbDirectory> newlist(new nsAbMDBDirProperty);
+ if (!newlist)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = newlist->CopyMailList(list);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dblist = do_QueryInterface(newlist, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDatabase->CreateMailListAndAddToDB(newlist, true, this);
+ }
+ else
+ mDatabase->CreateMailListAndAddToDB(list, true, this);
+
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+
+ uint32_t dbRowID;
+ dblist->GetDbRowID(&dbRowID);
+
+ nsAutoCString listUri(mURI);
+ listUri.AppendLiteral("/MailList");
+ listUri.AppendInt(dbRowID);
+
+ nsCOMPtr<nsIAbDirectory> newList;
+ rv = AddDirectory(listUri.get(), getter_AddRefs(newList));
+ if (NS_SUCCEEDED(rv) && newList)
+ {
+ nsCOMPtr<nsIAbMDBDirectory> dbnewList(do_QueryInterface(newList, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dbnewList->CopyDBMailList(dblist);
+ AddMailListToDirectory(newList);
+ NotifyItemAdded(newList);
+ }
+
+ NS_IF_ADDREF(*addedList = newList);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::AddCard(nsIAbCard* card, nsIAbCard **addedCard)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if (NS_FAILED(rv) || !mDatabase)
+ return NS_ERROR_FAILURE;
+
+ if (m_IsMailList)
+ rv = mDatabase->CreateNewListCardAndAddToDB(this, m_dbRowID, card, true /* notify */);
+ else
+ rv = mDatabase->CreateNewCardAndAddToDB(card, true, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+
+ NS_IF_ADDREF(*addedCard = card);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::ModifyCard(nsIAbCard *aModifiedCard)
+{
+ NS_ENSURE_ARG_POINTER(aModifiedCard);
+
+ nsresult rv;
+ if (!mDatabase)
+ {
+ rv = GetAbDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDatabase->EditCard(aModifiedCard, true, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::DropCard(nsIAbCard* aCard, bool needToCopyCard)
+{
+ NS_ENSURE_ARG_POINTER(aCard);
+
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsresult rv = NS_OK;
+
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if (NS_FAILED(rv) || !mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbCard> newCard;
+
+ if (needToCopyCard) {
+ newCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = newCard->Copy(aCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ newCard = aCard;
+ }
+
+ if (m_IsMailList) {
+ if (needToCopyCard) {
+ nsCOMPtr <nsIMdbRow> cardRow;
+ // if card doesn't exist in db, add the card to the directory that
+ // contains the mailing list.
+ mDatabase->FindRowByCard(newCard, getter_AddRefs(cardRow));
+ if (!cardRow)
+ mDatabase->CreateNewCardAndAddToDB(newCard, true /* notify */, this);
+ else
+ mDatabase->InitCardFromRow(newCard, cardRow);
+ }
+ // since we didn't copy the card, we don't have to notify that it was inserted
+ mDatabase->CreateNewListCardAndAddToDB(this, m_dbRowID, newCard, false /* notify */);
+ }
+ else {
+ mDatabase->CreateNewCardAndAddToDB(newCard, true /* notify */, this);
+ }
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::EditMailListToDatabase(nsIAbCard *listCard)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (!m_IsMailList)
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv = GetAbDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDatabase->EditMailList(this, listCard, true);
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+
+ return NS_OK;
+}
+
+static bool ContainsDirectory(nsIAbDirectory *parent, nsIAbDirectory *directory)
+{
+ // If parent is a maillist, 'addressLists' contains AbCards.
+ bool bIsMailList = false;
+ nsresult rv = parent->GetIsMailList(&bIsMailList);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (bIsMailList)
+ return false;
+
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ parent->GetAddressLists(getter_AddRefs(pAddressLists));
+ if (pAddressLists)
+ {
+ uint32_t total;
+ rv = pAddressLists->GetLength(&total);
+ for (uint32_t i = 0; i < total; ++i)
+ {
+ nsCOMPtr<nsIAbDirectory> pList(do_QueryElementAt(pAddressLists, i, &rv));
+
+ if (directory == pList)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// nsIAddrDBListener methods
+
+NS_IMETHODIMP nsAbMDBDirectory::OnCardAttribChange(uint32_t abCode)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::OnCardEntryChange
+(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent)
+{
+ // Don't notify AbManager unless we have the parent
+ if (!aParent)
+ return NS_OK;
+
+ NS_ENSURE_ARG_POINTER(aCard);
+ nsCOMPtr<nsISupports> cardSupports(do_QueryInterface(aCard));
+ nsresult rv;
+
+ // Notify when
+ // - any operation is done to a card belonging to this
+ // => if <this> is <aParent>, or
+ // - a card belonging to a directory which is parent of this is deleted
+ // => if aAbCode is AB_NotifyDeleted && <this> is child of <aParent>, or
+ // - a card belonging to a directory which is child of this is added/modified
+ // => if aAbCode is !AB_NotifyDeleted && <this> is parent of <aParent>
+
+ if (aParent != this)
+ {
+ bool isChild = false;
+ if (aAbCode != AB_NotifyDeleted)
+ isChild = ContainsDirectory(this, aParent);
+ else
+ isChild = ContainsDirectory(aParent, this);
+
+ if (!isChild)
+ return NS_OK;
+ }
+
+ switch (aAbCode) {
+ case AB_NotifyInserted:
+ rv = NotifyItemAdded(cardSupports);
+ break;
+ case AB_NotifyDeleted:
+ rv = NotifyItemDeleted(cardSupports);
+ break;
+ case AB_NotifyPropertyChanged:
+ rv = NotifyItemChanged(cardSupports);
+ break;
+ default:
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::OnListEntryChange
+(uint32_t abCode, nsIAbDirectory *list)
+{
+ nsresult rv = NS_OK;
+
+ if (abCode == AB_NotifyPropertyChanged && list)
+ {
+ bool bIsMailList = false;
+ rv = list->GetIsMailList(&bIsMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (bIsMailList) {
+ nsString listName;
+ rv = list->GetDirName(listName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = NotifyPropertyChanged(list, "DirName", nullptr, listName.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::OnAnnouncerGoingAway()
+{
+ if (mDatabase)
+ mDatabase->RemoveListener(this);
+ return NS_OK;
+}
+
+// nsIAbDirectorySearch methods
+
+NS_IMETHODIMP nsAbMDBDirectory::StartSearch()
+{
+ if (!mIsQueryURI)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+
+ mPerformingQuery = true;
+ mSearchCache.Clear();
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression;
+ rv = nsAbQueryStringToExpression::Convert(mQueryString,
+ getter_AddRefs(expression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetExpression(expression);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // don't search the subdirectories
+ // if the current directory is a mailing list, it won't have any subdirectories
+ // if the current directory is a addressbook, searching both it
+ // and the subdirectories (the mailing lists), will yield duplicate results
+ // because every entry in a mailing list will be an entry in the parent addressbook
+ rv = arguments->SetQuerySubDirectories(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the directory without the query
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bug 280232 - something was causing continuous loops in searching. Add a
+ // check here for the directory to search not being a query uri as well in
+ // the hopes that will at least break us out of the continuous loop even if
+ // we don't know how we got into it.
+ bool isQuery;
+ rv = directory->GetIsQuery(&isQuery);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isQuery)
+ {
+ NS_ERROR("Attempting to search a directory within a search");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Initiate the proxy query with the no query directory
+ nsCOMPtr<nsIAbDirectoryQueryProxy> queryProxy =
+ do_CreateInstance(NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = queryProxy->Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = queryProxy->DoQuery(directory, arguments, this, -1, 0, &mContext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::StopSearch()
+{
+ if (!mIsQueryURI)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+
+// nsAbDirSearchListenerContext methods
+
+NS_IMETHODIMP nsAbMDBDirectory::OnSearchFinished(int32_t aResult,
+ const nsAString &aErrorMsg)
+{
+ mPerformingQuery = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::OnSearchFoundCard(nsIAbCard* card)
+{
+ mSearchCache.Put(card, card);
+
+ // TODO
+ // Search is synchronous so asserting on the
+ // datasource will not work since the getChildCards
+ // method will not have returned with results.
+ // NotifyItemAdded (card);
+ return NS_OK;
+}
+
+nsresult nsAbMDBDirectory::GetAbDatabase()
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (mDatabase)
+ return NS_OK;
+
+ nsresult rv;
+
+ if (m_IsMailList)
+ {
+ // Get the database of the parent directory.
+ nsAutoCString parentURI(mURINoQuery);
+
+ int32_t pos = parentURI.RFindChar('/');
+
+ // If we didn't find a / something really bad has happened
+ if (pos == -1)
+ return NS_ERROR_FAILURE;
+
+ parentURI = StringHead(parentURI, pos);
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(parentURI, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbMDBDirectory> mdbDir(do_QueryInterface(directory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mdbDir->GetDatabase(getter_AddRefs(mDatabase));
+ }
+ else
+ rv = GetDatabase(getter_AddRefs(mDatabase));
+
+ if (NS_SUCCEEDED(rv))
+ rv = mDatabase->AddListener(this);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::CardForEmailAddress(const nsACString &aEmailAddress, nsIAbCard ** aAbCard)
+{
+ NS_ENSURE_ARG_POINTER(aAbCard);
+
+ *aAbCard = nullptr;
+
+ // Ensure that if we've not been given an email address we never match
+ // so that we don't fail out unnecessarily and we don't match a blank email
+ // address against random cards that the user hasn't supplied an email for.
+ if (aEmailAddress.IsEmpty())
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ rv = GetAbDatabase();
+ if (rv == NS_ERROR_FILE_NOT_FOUND)
+ {
+ // If file wasn't found, the card cannot exist.
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert Email to lower case in UTF-16 format. This correctly lower-cases
+ // it and doing this change means that we can use a hash lookup in the
+ // database rather than searching and comparing each line individually.
+ NS_ConvertUTF8toUTF16 lowerEmail(aEmailAddress);
+ ToLowerCase(lowerEmail);
+
+ // If lower email is empty, something went wrong somewhere, e.g. the conversion.
+ // Hence, don't go looking for a card with no email address. Something is wrong.
+ if (lowerEmail.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ mDatabase->GetCardFromAttribute(this, kLowerPriEmailColumn,
+ NS_ConvertUTF16toUTF8(lowerEmail),
+ false, aAbCard);
+ if (!*aAbCard)
+ {
+ mDatabase->GetCardFromAttribute(this, kLower2ndEmailColumn,
+ NS_ConvertUTF16toUTF8(lowerEmail),
+ false, aAbCard);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsIAbCard **result)
+{
+ NS_ENSURE_ARG(aProperty);
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = nullptr;
+
+ // If the value is empty, don't match.
+ if (aValue.IsEmpty())
+ return NS_OK;
+
+ nsresult rv;
+ if (!mDatabase)
+ {
+ rv = GetAbDatabase();
+ // We can't find the database file, so we can't find the card at all.
+ if (rv == NS_ERROR_FILE_NOT_FOUND)
+ return NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // nsIAddrDatabase has aCaseInsensitive as its parameter
+ return mDatabase->GetCardFromAttribute(this, aProperty, aValue,
+ !caseSensitive, result);
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsISimpleEnumerator **result)
+{
+ NS_ENSURE_ARG(aProperty);
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = nullptr;
+
+ if (aValue.IsEmpty())
+ return NS_OK;
+
+ if (!mDatabase)
+ {
+ nsresult rv = GetAbDatabase();
+ if (rv == NS_ERROR_FILE_NOT_FOUND)
+ return NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mDatabase->GetCardsFromAttribute(this, aProperty, aValue,
+ !caseSensitive, result);
+}
diff --git a/mailnews/addrbook/src/nsAbMDBDirectory.h b/mailnews/addrbook/src/nsAbMDBDirectory.h
new file mode 100644
index 000000000..fb25c5708
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirectory.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Directory
+
+*********************************************************************************************************/
+
+#ifndef nsAbMDBDirectory_h__
+#define nsAbMDBDirectory_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbMDBDirProperty.h"
+#include "nsIAbCard.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsDirPrefs.h"
+#include "nsIAbDirectorySearch.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIAddrDBListener.h"
+
+/*
+ * Address Book Directory
+ */
+
+class nsAbMDBDirectory:
+ public nsAbMDBDirProperty, // nsIAbDirectory, nsIAbMDBDirectory
+ public nsIAbDirSearchListener,
+ public nsIAddrDBListener,
+ public nsIAbDirectorySearch
+{
+public:
+ nsAbMDBDirectory(void);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIADDRDBLISTENER
+
+ // Override nsAbMDBDirProperty::Init
+ NS_IMETHOD Init(const char *aUri) override;
+
+ // nsIAbMDBDirectory methods
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+ NS_IMETHOD ClearDatabase() override;
+ NS_IMETHOD NotifyDirItemAdded(nsISupports *item) override { return NotifyItemAdded(item);}
+ NS_IMETHOD RemoveElementsFromAddressList() override;
+ NS_IMETHOD RemoveEmailAddressAt(uint32_t aIndex) override;
+ NS_IMETHOD AddDirectory(const char *uriName, nsIAbDirectory **childDir) override;
+ NS_IMETHOD GetDatabaseFile(nsIFile **aResult) override;
+ NS_IMETHOD GetDatabase(nsIAddrDatabase **aResult) override;
+
+ // nsIAbDirectory methods:
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD GetChildCards(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD GetIsQuery(bool *aResult) override;
+ NS_IMETHOD DeleteDirectory(nsIAbDirectory *directory) override;
+ NS_IMETHOD DeleteCards(nsIArray *cards) override;
+ NS_IMETHOD HasCard(nsIAbCard *cards, bool *hasCard) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory *dir, bool *hasDir) override;
+ NS_IMETHOD HasMailListWithName(const char16_t *aName, bool *aHasList) override;
+ NS_IMETHOD AddMailList(nsIAbDirectory *list, nsIAbDirectory **addedList) override;
+ NS_IMETHOD AddCard(nsIAbCard *card, nsIAbCard **addedCard) override;
+ NS_IMETHOD ModifyCard(nsIAbCard *aModifiedCard) override;
+ NS_IMETHOD DropCard(nsIAbCard *card, bool needToCopyCard) override;
+ NS_IMETHOD EditMailListToDatabase(nsIAbCard *listCard) override;
+ NS_IMETHOD CardForEmailAddress(const nsACString &aEmailAddress,
+ nsIAbCard ** aAbCard) override;
+ NS_IMETHOD GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive, nsIAbCard **result) override;
+ NS_IMETHOD GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsISimpleEnumerator **result) override;
+
+ // nsIAbDirectorySearch methods
+ NS_DECL_NSIABDIRECTORYSEARCH
+
+ // nsIAbDirSearchListener methods
+ NS_DECL_NSIABDIRSEARCHLISTENER
+
+protected:
+ virtual ~nsAbMDBDirectory();
+ nsresult NotifyPropertyChanged(nsIAbDirectory *list, const char *property, const char16_t* oldValue, const char16_t* newValue);
+ nsresult NotifyItemAdded(nsISupports *item);
+ nsresult NotifyItemDeleted(nsISupports *item);
+ nsresult NotifyItemChanged(nsISupports *item);
+ nsresult RemoveCardFromAddressList(nsIAbCard* card);
+
+ nsresult GetAbDatabase();
+ nsCOMPtr<nsIAddrDatabase> mDatabase;
+
+ nsCOMArray<nsIAbDirectory> mSubDirectories;
+
+ int32_t mContext;
+ bool mPerformingQuery;
+
+ nsInterfaceHashtable<nsISupportsHashKey, nsIAbCard> mSearchCache;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbManager.cpp b/mailnews/addrbook/src/nsAbManager.cpp
new file mode 100644
index 000000000..2de1b4468
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbManager.cpp
@@ -0,0 +1,1422 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbManager.h"
+#include "nsAbBaseCID.h"
+#include "nsAddrDatabase.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsMsgI18N.h"
+#include "nsIStringBundle.h"
+#include "nsMsgUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "nsIServiceManager.h"
+#include "mozIDOMWindow.h"
+#include "nsIFilePicker.h"
+#include "plbase64.h"
+#include "nsIWindowWatcher.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsVCard.h"
+#include "nsVCardObj.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsICommandLine.h"
+#include "nsIFile.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIObserverService.h"
+#include "nsDirPrefs.h"
+#include "nsThreadUtils.h"
+#include "nsIAbDirFactory.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIIOService.h"
+#include "nsAbQueryStringToExpression.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+using namespace mozilla;
+
+struct ExportAttributesTableStruct
+{
+ const char* abPropertyName;
+ uint32_t plainTextStringID;
+};
+
+// our schema is not fixed yet, but we still want some sort of objectclass
+// for now, use obsolete in the class name, hinting that this will change
+// see bugs bug #116692 and #118454
+#define MOZ_AB_OBJECTCLASS "mozillaAbPersonAlpha"
+
+// for now, the oder of the attributes with true for includeForPlainText
+// should be in the same order as they are in the import code
+// see importMsgProperties and nsImportStringBundle.
+//
+// XXX todo, merge with what's in nsAbLDAPProperties.cpp, so we can
+// use this for LDAP and LDIF export
+//
+// here's how we're coming up with the ldapPropertyName values
+// if they are specified in RFC 2798, use them
+// else use the 4.x LDIF attribute names (for example, "xmozillanickname"
+// as we want to allow export from mozilla back to 4.x, and other apps
+// are probably out there that can handle 4.x LDIF)
+// else use the MOZ_AB_LDIF_PREFIX prefix, see nsIAddrDatabase.idl
+
+const ExportAttributesTableStruct EXPORT_ATTRIBUTES_TABLE[] = {
+ {kFirstNameProperty, 2100},
+ {kLastNameProperty, 2101},
+ {kDisplayNameProperty, 2102},
+ {kNicknameProperty, 2103},
+ {kPriEmailProperty, 2104},
+ {k2ndEmailProperty, 2105},
+ {kScreenNameProperty, 2136},
+ {kPreferMailFormatProperty, 0},
+ {kLastModifiedDateProperty, 0},
+ {kWorkPhoneProperty, 2106},
+ {kWorkPhoneTypeProperty, 0},
+ {kHomePhoneProperty, 2107},
+ {kHomePhoneTypeProperty, 0},
+ {kFaxProperty, 2108},
+ {kFaxTypeProperty, 0},
+ {kPagerProperty, 2109},
+ {kPagerTypeProperty, 0},
+ {kCellularProperty, 2110},
+ {kCellularTypeProperty, 0},
+ {kHomeAddressProperty, 2111},
+ {kHomeAddress2Property, 2112},
+ {kHomeCityProperty, 2113},
+ {kHomeStateProperty, 2114},
+ {kHomeZipCodeProperty, 2115},
+ {kHomeCountryProperty, 2116},
+ {kWorkAddressProperty, 2117},
+ {kWorkAddress2Property, 2118},
+ {kWorkCityProperty, 2119},
+ {kWorkStateProperty, 2120},
+ {kWorkZipCodeProperty, 2121},
+ {kWorkCountryProperty, 2122},
+ {kJobTitleProperty, 2123},
+ {kDepartmentProperty, 2124},
+ {kCompanyProperty, 2125},
+ {kWorkWebPageProperty, 2126},
+ {kHomeWebPageProperty, 2127},
+ {kBirthYearProperty, 2128}, // unused for now
+ {kBirthMonthProperty, 2129}, // unused for now
+ {kBirthDayProperty, 2130}, // unused for now
+ {kCustom1Property, 2131},
+ {kCustom2Property, 2132},
+ {kCustom3Property, 2133},
+ {kCustom4Property, 2134},
+ {kNotesProperty, 2135},
+ {kAnniversaryYearProperty, 0},
+ {kAnniversaryMonthProperty, 0},
+ {kAnniversaryDayProperty, 0},
+ {kSpouseNameProperty, 0},
+ {kFamilyNameProperty, 0},
+};
+
+//
+// nsAbManager
+//
+nsAbManager::nsAbManager()
+{
+}
+
+nsAbManager::~nsAbManager()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAbManager, nsIAbManager, nsICommandLineHandler,
+ nsIObserver)
+
+nsresult nsAbManager::Init()
+{
+ NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = observerService->AddObserver(this, "profile-do-change", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *someData)
+{
+ // The nsDirPrefs code caches all the directories that it got
+ // from the first profiles prefs.js.
+ // When we profile switch, we need to force it to shut down.
+ // We'll re-load all the directories from the second profiles prefs.js
+ // that happens in nsAbBSDirectory::GetChildNodes()
+ // when we call DIR_GetDirectories().
+ if (!strcmp(aTopic, "profile-do-change"))
+ {
+ DIR_ShutDown();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ {
+ DIR_ShutDown();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = observerService->RemoveObserver(this, "profile-do-change");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+//
+// nsIAbManager
+//
+
+NS_IMETHODIMP nsAbManager::GetDirectories(nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // We cache the top level AB to ensure that nsIAbDirectory items are not
+ // created and dumped every time GetDirectories is called. This was causing
+ // performance problems, especially with the content policy on messages
+ // with lots of urls.
+ nsresult rv;
+ nsCOMPtr<nsIAbDirectory> rootAddressBook;
+ rv = GetRootDirectory(getter_AddRefs(rootAddressBook));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rootAddressBook->GetChildNodes(aResult);
+}
+
+nsresult
+nsAbManager::GetRootDirectory(nsIAbDirectory **aResult)
+{
+ // We cache the top level AB to ensure that nsIAbDirectory items are not
+ // created and dumped every time GetDirectories is called. This was causing
+ // performance problems, especially with the content policy on messages
+ // with lots of urls.
+ nsresult rv;
+
+ if (!mCacheTopLevelAb)
+ {
+ nsCOMPtr<nsIAbDirectory> rootAddressBook(do_GetService(NS_ABDIRECTORY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCacheTopLevelAb = rootAddressBook;
+ }
+
+ NS_IF_ADDREF(*aResult = mCacheTopLevelAb);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::GetDirectoryFromId(const nsACString &aDirPrefId,
+ nsIAbDirectory **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = GetDirectories(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISupports> support;
+ nsCOMPtr<nsIAbDirectory> directory;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
+ rv = enumerator->GetNext(getter_AddRefs(support));
+ NS_ENSURE_SUCCESS(rv, rv);
+ directory = do_QueryInterface(support, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unable to select Address book nsAbManager::GetDirectoryFromId()");
+ continue;
+ }
+
+ nsCString dirPrefId;
+ directory->GetDirPrefId(dirPrefId);
+ if (dirPrefId.Equals(aDirPrefId)) {
+ directory.forget(aResult);
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::GetDirectory(const nsACString &aURI,
+ nsIAbDirectory **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIAbDirectory> directory;
+
+ // Was the directory root requested?
+ if (aURI.EqualsLiteral(kAllDirectoryRoot))
+ {
+ rv = GetRootDirectory(getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_IF_ADDREF(*aResult = directory);
+ return NS_OK;
+ }
+
+ // Do we have a copy of this directory already within our look-up table?
+ if (!mAbStore.Get(aURI, getter_AddRefs(directory)))
+ {
+ // The directory wasn't in our look-up table, so we need to instantiate
+ // it. First, extract the scheme from the URI...
+
+ nsAutoCString scheme;
+
+ int32_t colon = aURI.FindChar(':');
+ if (colon <= 0)
+ return NS_ERROR_MALFORMED_URI;
+ scheme = Substring(aURI, 0, colon);
+
+ // Construct the appropriate nsIAbDirectory...
+ nsAutoCString contractID;
+ contractID.AssignLiteral(NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX);
+ contractID.Append(scheme);
+ directory = do_CreateInstance(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Init it with the URI
+ rv = directory->Init(PromiseFlatCString(aURI).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if this directory was initiated with a search query. If so,
+ // we don't cache it.
+ bool isQuery = false;
+ rv = directory->GetIsQuery(&isQuery);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isQuery)
+ mAbStore.Put(aURI, directory);
+ }
+ NS_IF_ADDREF(*aResult = directory);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::NewAddressBook(const nsAString &aDirName,
+ const nsACString &aURI,
+ const uint32_t aType,
+ const nsACString &aPrefName,
+ nsACString &aResult)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIAbDirectory> parentDir;
+ rv = GetRootDirectory(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return parentDir->CreateNewDirectory(aDirName, aURI, aType, aPrefName, aResult);
+}
+
+NS_IMETHODIMP nsAbManager::DeleteAddressBook(const nsACString &aURI)
+{
+ // Find the address book
+ nsresult rv;
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = GetDirectory(aURI, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> rootDirectory;
+ rv = GetRootDirectory(getter_AddRefs(rootDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Go through each of the children of the address book
+ // (so, the mailing lists) and remove their entries from
+ // the look up table.
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = directory->GetChildNodes(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> item;
+ nsCOMPtr<nsIAbDirectory> childDirectory;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ childDirectory = do_QueryInterface(item, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString childURI;
+ rv = childDirectory->GetURI(childURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mAbStore.Remove(childURI);
+ }
+ }
+
+ mAbStore.Remove(aURI);
+
+ bool isMailList;
+ rv = directory->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isMailList)
+ // If we're not a mailing list, then our parent
+ // must be the root address book directory.
+ return rootDirectory->DeleteDirectory(directory);
+
+ nsCString parentUri;
+ parentUri.Append(aURI);
+ int32_t pos = parentUri.RFindChar('/');
+
+ // If we didn't find a /, we're in trouble.
+ if (pos == -1)
+ return NS_ERROR_FAILURE;
+
+ parentUri = StringHead(parentUri, pos);
+ nsCOMPtr<nsIAbDirectory> parentDirectory;
+ rv = GetDirectory(parentUri, getter_AddRefs(parentDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return parentDirectory->DeleteDirectory(directory);
+}
+
+NS_IMETHODIMP nsAbManager::AddAddressBookListener(nsIAbListener *aListener,
+ abListenerNotifyFlagValue aNotifyFlags)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ abListener newListener(aListener, aNotifyFlags);
+ mListeners.AppendElementUnlessExists(newListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::RemoveAddressBookListener(nsIAbListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+#define NOTIFY_AB_LISTENERS(propertyflag_, propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<abListener>::ForwardIterator iter(mListeners); \
+ while (iter.HasMore()) { \
+ const abListener &abL = iter.GetNext(); \
+ if (abL.mNotifyFlags & nsIAbListener::propertyflag_) \
+ abL.mListener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP nsAbManager::NotifyItemPropertyChanged(nsISupports *aItem,
+ const char *aProperty,
+ const char16_t* aOldValue,
+ const char16_t* aNewValue)
+{
+ NOTIFY_AB_LISTENERS(itemChanged, OnItemPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::NotifyDirectoryItemAdded(nsIAbDirectory *aParentDirectory,
+ nsISupports *aItem)
+{
+ NOTIFY_AB_LISTENERS(itemAdded, OnItemAdded, (aParentDirectory, aItem));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::NotifyDirectoryItemDeleted(nsIAbDirectory *aParentDirectory,
+ nsISupports *aItem)
+{
+ NOTIFY_AB_LISTENERS(directoryItemRemoved, OnItemRemoved,
+ (aParentDirectory, aItem));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::NotifyDirectoryDeleted(nsIAbDirectory *aParentDirectory,
+ nsISupports *aDirectory)
+{
+ NOTIFY_AB_LISTENERS(directoryRemoved, OnItemRemoved,
+ (aParentDirectory, aDirectory));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::GetUserProfileDirectory(nsIFile **userDir)
+{
+ NS_ENSURE_ARG_POINTER(userDir);
+ *userDir = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> profileDir;
+ nsAutoCString pathBuf;
+
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ profileDir.forget(userDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::MailListNameExists(const char16_t *aName, bool *aExists)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aExists);
+
+ *aExists = false;
+
+ // now get the top-level book
+ nsCOMPtr<nsIAbDirectory> topDirectory;
+ rv = GetRootDirectory(getter_AddRefs(topDirectory));
+
+ // now go through the address books
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = topDirectory->GetChildNodes(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory = do_QueryInterface(item, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ rv = directory->HasMailListWithName(aName, aExists);
+ if (NS_SUCCEEDED(rv) && *aExists)
+ return NS_OK;
+ }
+
+ *aExists = false;
+ return NS_OK;
+}
+
+#define CSV_DELIM ","
+#define CSV_DELIM_LEN 1
+#define TAB_DELIM "\t"
+#define TAB_DELIM_LEN 1
+
+#define CSV_FILE_EXTENSION ".csv"
+#define TAB_FILE_EXTENSION ".tab"
+#define TXT_FILE_EXTENSION ".txt"
+#define VCF_FILE_EXTENSION ".vcf"
+#define LDIF_FILE_EXTENSION ".ldi"
+#define LDIF_FILE_EXTENSION2 ".ldif"
+
+enum ADDRESSBOOK_EXPORT_FILE_TYPE
+{
+ CSV_EXPORT_TYPE = 0,
+ CSV_EXPORT_TYPE_UTF8 = 1,
+ TAB_EXPORT_TYPE = 2,
+ TAB_EXPORT_TYPE_UTF8 = 3,
+ VCF_EXPORT_TYPE = 4,
+ LDIF_EXPORT_TYPE = 5,
+};
+
+NS_IMETHODIMP nsAbManager::ExportAddressBook(mozIDOMWindowProxy *aParentWin, nsIAbDirectory *aDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aParentWin);
+
+ nsresult rv;
+ nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString dirName;
+ aDirectory->GetDirName(dirName);
+ const char16_t *formatStrings[] = { dirName.get() };
+
+ nsString title;
+ rv = bundle->FormatStringFromName(u"ExportAddressBookNameTitle", formatStrings,
+ ArrayLength(formatStrings), getter_Copies(title));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filePicker->Init(aParentWin, title, nsIFilePicker::modeSave);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filePicker->SetDefaultString(dirName);
+
+ nsString filterString;
+
+ // CSV: System charset and UTF-8.
+ rv = bundle->GetStringFromName(u"CSVFilesSysCharset", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.csv"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->GetStringFromName(u"CSVFilesUTF8", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.csv"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Tab separated: System charset and UTF-8.
+ rv = bundle->GetStringFromName(u"TABFilesSysCharset", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.tab; *.txt"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->GetStringFromName(u"TABFilesUTF8", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.tab; *.txt"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = bundle->GetStringFromName(u"VCFFiles", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.vcf"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = bundle->GetStringFromName(u"LDIFFiles", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.ldi; *.ldif"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int16_t dialogResult;
+ filePicker->Show(&dialogResult);
+
+ if (dialogResult == nsIFilePicker::returnCancel)
+ return rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dialogResult == nsIFilePicker::returnReplace) {
+ // be extra safe and only delete when the file is really a file
+ bool isFile;
+ rv = localFile->IsFile(&isFile);
+ if (NS_SUCCEEDED(rv) && isFile) {
+ rv = localFile->Remove(false /* recursive delete */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // The type of export is determined by the drop-down in
+ // the file picker dialog.
+ int32_t exportType;
+ rv = filePicker->GetFilterIndex(&exportType);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoString fileName;
+ rv = localFile->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch ( exportType )
+ {
+ default:
+ case LDIF_EXPORT_TYPE: // ldif
+ // If filename does not have the correct ext, add one.
+ if ((MsgFind(fileName, LDIF_FILE_EXTENSION, true, fileName.Length() - strlen(LDIF_FILE_EXTENSION)) == -1) &&
+ (MsgFind(fileName, LDIF_FILE_EXTENSION2, true, fileName.Length() - strlen(LDIF_FILE_EXTENSION2)) == -1)) {
+
+ // Add the extension and build a new localFile.
+ fileName.AppendLiteral(LDIF_FILE_EXTENSION2);
+ localFile->SetLeafName(fileName);
+ }
+ rv = ExportDirectoryToLDIF(aDirectory, localFile);
+ break;
+
+ case CSV_EXPORT_TYPE: // csv
+ case CSV_EXPORT_TYPE_UTF8:
+ // If filename does not have the correct ext, add one.
+ if (MsgFind(fileName, CSV_FILE_EXTENSION, true, fileName.Length() - strlen(CSV_FILE_EXTENSION)) == -1) {
+
+ // Add the extension and build a new localFile.
+ fileName.AppendLiteral(CSV_FILE_EXTENSION);
+ localFile->SetLeafName(fileName);
+ }
+ rv = ExportDirectoryToDelimitedText(aDirectory, CSV_DELIM, CSV_DELIM_LEN, localFile,
+ exportType==CSV_EXPORT_TYPE_UTF8);
+ break;
+
+ case TAB_EXPORT_TYPE: // tab & text
+ case TAB_EXPORT_TYPE_UTF8:
+ // If filename does not have the correct ext, add one.
+ if ((MsgFind(fileName, TXT_FILE_EXTENSION, true, fileName.Length() - strlen(TXT_FILE_EXTENSION)) == -1) &&
+ (MsgFind(fileName, TAB_FILE_EXTENSION, true, fileName.Length() - strlen(TAB_FILE_EXTENSION)) == -1)) {
+
+ // Add the extension and build a new localFile.
+ fileName.AppendLiteral(TXT_FILE_EXTENSION);
+ localFile->SetLeafName(fileName);
+ }
+ rv = ExportDirectoryToDelimitedText(aDirectory, TAB_DELIM, TAB_DELIM_LEN, localFile,
+ exportType==TAB_EXPORT_TYPE_UTF8);
+ break;
+
+ case VCF_EXPORT_TYPE: // vCard
+ // If filename does not have the correct ext, add one.
+ if (MsgFind(fileName, VCF_FILE_EXTENSION, true, fileName.Length() - strlen(VCF_FILE_EXTENSION)) == -1) {
+
+ // Add the extension and build a new localFile.
+ fileName.AppendLiteral(VCF_FILE_EXTENSION);
+ localFile->SetLeafName(fileName);
+ }
+ rv = ExportDirectoryToVCard(aDirectory, localFile);
+ break;
+ };
+
+ return rv;
+}
+
+nsresult
+nsAbManager::ExportDirectoryToDelimitedText(nsIAbDirectory *aDirectory,
+ const char *aDelim,
+ uint32_t aDelimLen,
+ nsIFile *aLocalFile,
+ bool useUTF8)
+{
+ nsCOMPtr <nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr <nsIAbCard> card;
+
+ nsresult rv;
+
+ nsCOMPtr <nsIOutputStream> outputStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream),
+ aLocalFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+
+ // the desired file may be read only
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t i;
+ uint32_t writeCount;
+ uint32_t length;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/importMsgs.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString revisedName;
+ nsString columnName;
+
+ for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) {
+ if (EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID != 0) {
+
+ // We don't need to truncate the string here as getter_Copies will
+ // do that for us.
+ if (NS_FAILED(bundle->GetStringFromID(EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID, getter_Copies(columnName))))
+ columnName.AppendInt(EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID);
+
+ rv = nsMsgI18NConvertFromUnicode(useUTF8 ? "UTF-8" : nsMsgI18NFileSystemCharset(),
+ columnName, revisedName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = outputStream->Write(revisedName.get(),
+ revisedName.Length(),
+ &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (revisedName.Length() != writeCount)
+ return NS_ERROR_FAILURE;
+
+ if (i < ArrayLength(EXPORT_ATTRIBUTES_TABLE) - 1) {
+ rv = outputStream->Write(aDelim, aDelimLen, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aDelimLen != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (MSG_LINEBREAK_LEN != writeCount)
+ return NS_ERROR_FAILURE;
+
+ rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator));
+ if (NS_SUCCEEDED(rv) && cardsEnumerator) {
+ nsCOMPtr<nsISupports> item;
+ bool more;
+ while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) {
+ rv = cardsEnumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isMailList;
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+
+ if (isMailList) {
+ // .tab, .txt and .csv aren't able to export mailing lists
+ // use LDIF for that.
+ }
+ else {
+ nsString value;
+ nsCString valueCStr;
+
+ for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) {
+ if (EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID != 0) {
+ rv = card->GetPropertyAsAString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, value);
+ if (NS_FAILED(rv))
+ value.Truncate();
+
+ // If a string contains at least one comma, tab or double quote then
+ // we need to quote the entire string. Also if double quote is part
+ // of the string we need to quote the double quote(s) as well.
+ nsAutoString newValue(value);
+ bool needsQuotes = false;
+ if(newValue.FindChar('"') != -1)
+ {
+ needsQuotes = true;
+
+ int32_t match = 0;
+ uint32_t offset = 0;
+ nsString oldSubstr = NS_LITERAL_STRING("\"");
+ nsString newSubstr = NS_LITERAL_STRING("\"\"");
+ while (offset < newValue.Length()) {
+ match = newValue.Find(oldSubstr, offset);
+ if (match == -1)
+ break;
+
+ newValue.Replace(offset + match, oldSubstr.Length(), newSubstr);
+ offset += (match + newSubstr.Length());
+ }
+ }
+ if (!needsQuotes && (newValue.FindChar(',') != -1 || newValue.FindChar('\x09') != -1))
+ needsQuotes = true;
+
+ // Make sure we quote if containing CR/LF.
+ if (newValue.FindChar('\r') != -1 ||
+ newValue.FindChar('\n') != -1)
+ needsQuotes = true;
+
+ if (needsQuotes)
+ {
+ newValue.Insert(NS_LITERAL_STRING("\""), 0);
+ newValue.AppendLiteral("\"");
+ }
+
+ rv = nsMsgI18NConvertFromUnicode(useUTF8 ? "UTF-8" : nsMsgI18NFileSystemCharset(),
+ newValue, valueCStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (NS_FAILED(rv)) {
+ NS_ERROR("failed to convert string to system charset. use LDIF");
+ valueCStr = "?";
+ }
+
+ length = valueCStr.Length();
+ if (length) {
+ rv = outputStream->Write(valueCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ valueCStr = "";
+ }
+ else {
+ // something we don't support for the current export
+ // for example, .tab doesn't export preferred html format
+ continue; // go to next field
+ }
+
+ if (i < ArrayLength(EXPORT_ATTRIBUTES_TABLE) - 1) {
+ rv = outputStream->Write(aDelim, aDelimLen, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (aDelimLen != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // write out the linebreak that separates the cards
+ rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (MSG_LINEBREAK_LEN != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+
+ rv = outputStream->Flush();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = outputStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsresult
+nsAbManager::ExportDirectoryToVCard(nsIAbDirectory *aDirectory, nsIFile *aLocalFile)
+{
+ nsCOMPtr <nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr <nsIAbCard> card;
+
+ nsresult rv;
+
+ nsCOMPtr <nsIOutputStream> outputStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream),
+ aLocalFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+
+ // the desired file may be read only
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t writeCount;
+ uint32_t length;
+
+ rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator));
+ if (NS_SUCCEEDED(rv) && cardsEnumerator) {
+ nsCOMPtr<nsISupports> item;
+ bool more;
+ while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) {
+ rv = cardsEnumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isMailList;
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isMailList) {
+ // we don't know how to export mailing lists to vcf
+ // use LDIF for that.
+ }
+ else {
+ nsCString escapedValue;
+ rv = card->TranslateTo(NS_LITERAL_CSTRING("vcard"), escapedValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString valueCStr;
+ MsgUnescapeString(escapedValue, 0, valueCStr);
+
+ length = valueCStr.Length();
+ rv = outputStream->Write(valueCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+
+ rv = outputStream->Flush();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = outputStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+
+nsresult
+nsAbManager::ExportDirectoryToLDIF(nsIAbDirectory *aDirectory, nsIFile *aLocalFile)
+{
+ nsCOMPtr <nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr <nsIAbCard> card;
+
+ nsresult rv;
+
+ nsCOMPtr <nsIOutputStream> outputStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream),
+ aLocalFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+
+ // the desired file may be read only
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Get the default attribute map for ldap. We use the default attribute
+ // map rather than one for a specific server because if people want an
+ // ldif export using a servers specific schema, then they can use ldapsearch
+ nsCOMPtr<nsIAbLDAPAttributeMapService> mapSrv =
+ do_GetService("@mozilla.org/addressbook/ldap-attribute-map-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ rv = mapSrv->GetMapForPrefBranch(NS_LITERAL_CSTRING("ldap_2.servers.default.attrmap"),
+ getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i;
+ uint32_t writeCount;
+ uint32_t length;
+
+ rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator));
+ if (NS_SUCCEEDED(rv) && cardsEnumerator) {
+ nsCOMPtr<nsISupports> item;
+ bool more;
+ while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) {
+ rv = cardsEnumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isMailList;
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isMailList) {
+ nsCString mailListCStr;
+
+ rv = AppendLDIFForMailList(card, attrMap, mailListCStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ length = mailListCStr.Length();
+ rv = outputStream->Write(mailListCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ else {
+ nsString value;
+ nsCString valueCStr;
+
+ rv = AppendBasicLDIFForCard(card, attrMap, valueCStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ length = valueCStr.Length();
+ rv = outputStream->Write(valueCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+
+ valueCStr.Truncate();
+
+ nsAutoCString ldapAttribute;
+
+ for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) {
+ if (NS_SUCCEEDED(attrMap->GetFirstAttribute(nsDependentCString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName),
+ ldapAttribute)) &&
+ !ldapAttribute.IsEmpty()) {
+
+ rv = card->GetPropertyAsAString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, value);
+ if (NS_FAILED(rv))
+ value.Truncate();
+
+ if (!PL_strcmp(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, kPreferMailFormatProperty)) {
+ if (value.EqualsLiteral("html"))
+ value.AssignLiteral("true");
+ else if (value.EqualsLiteral("plaintext"))
+ value.AssignLiteral("false");
+ else
+ value.Truncate(); // unknown.
+ }
+
+ if (!value.IsEmpty()) {
+ rv = AppendProperty(ldapAttribute.get(), value.get(), valueCStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ valueCStr += MSG_LINEBREAK;
+ }
+ else
+ valueCStr.Truncate();
+
+ length = valueCStr.Length();
+ if (length) {
+ rv = outputStream->Write(valueCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ valueCStr.Truncate();
+ }
+ else {
+ // something we don't support yet
+ // ldif doesn't export multiple addresses
+ }
+ }
+
+ // write out the linebreak that separates the cards
+ rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (MSG_LINEBREAK_LEN != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+
+ rv = outputStream->Flush();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = outputStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsresult nsAbManager::AppendLDIFForMailList(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult)
+{
+ nsresult rv;
+ nsString attrValue;
+
+ rv = AppendDNForCard("dn", aCard, aAttrMap, aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ aResult += MSG_LINEBREAK \
+ "objectclass: top" MSG_LINEBREAK \
+ "objectclass: groupOfNames" MSG_LINEBREAK;
+
+ rv = aCard->GetDisplayName(attrValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString ldapAttributeName;
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kDisplayNameProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult += MSG_LINEBREAK;
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kNicknameProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aCard->GetPropertyAsAString(kNicknameProperty, attrValue);
+ if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) {
+ rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult += MSG_LINEBREAK;
+ }
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kNotesProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aCard->GetPropertyAsAString(kNotesProperty, attrValue);
+ if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) {
+ rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult += MSG_LINEBREAK;
+ }
+
+ nsCString mailListURI;
+ rv = aCard->GetMailListURI(getter_Copies(mailListURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbDirectory> mailList;
+ rv = GetDirectory(mailListURI, getter_AddRefs(mailList));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMutableArray> addresses;
+ rv = mailList->GetAddressLists(getter_AddRefs(addresses));
+ if (addresses) {
+ uint32_t total = 0;
+ addresses->GetLength(&total);
+ if (total) {
+ uint32_t i;
+ for (i = 0; i < total; i++) {
+ nsCOMPtr <nsIAbCard> listCard = do_QueryElementAt(addresses, i, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AppendDNForCard("member", listCard, aAttrMap, aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ aResult += MSG_LINEBREAK;
+ }
+ }
+ }
+
+ aResult += MSG_LINEBREAK;
+ return NS_OK;
+}
+
+nsresult nsAbManager::AppendDNForCard(const char *aProperty, nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult)
+{
+ nsString email;
+ nsString displayName;
+ nsAutoCString ldapAttributeName;
+
+ nsresult rv = aCard->GetPrimaryEmail(email);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aCard->GetDisplayName(displayName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString cnStr;
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kDisplayNameProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!displayName.IsEmpty()) {
+ cnStr += NS_ConvertUTF8toUTF16(ldapAttributeName).get();
+ cnStr.AppendLiteral("=");
+ cnStr.Append(displayName);
+ if (!email.IsEmpty()) {
+ cnStr.AppendLiteral(",");
+ }
+ }
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kPriEmailProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!email.IsEmpty()) {
+ cnStr += NS_ConvertUTF8toUTF16(ldapAttributeName).get();
+ cnStr.AppendLiteral("=");
+ cnStr.Append(email);
+ }
+
+ rv = AppendProperty(aProperty, cnStr.get(), aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsAbManager::AppendBasicLDIFForCard(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult)
+{
+ nsresult rv = AppendDNForCard("dn", aCard, aAttrMap, aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult += MSG_LINEBREAK \
+ "objectclass: top" MSG_LINEBREAK \
+ "objectclass: person" MSG_LINEBREAK \
+ "objectclass: organizationalPerson" MSG_LINEBREAK \
+ "objectclass: inetOrgPerson" MSG_LINEBREAK \
+ "objectclass: " MOZ_AB_OBJECTCLASS MSG_LINEBREAK;
+ return rv;
+}
+
+bool nsAbManager::IsSafeLDIFString(const char16_t *aStr)
+{
+ // follow RFC 2849 to determine if something is safe "as is" for LDIF
+ if (aStr[0] == char16_t(' ') ||
+ aStr[0] == char16_t(':') ||
+ aStr[0] == char16_t('<'))
+ return false;
+
+ uint32_t i;
+ uint32_t len = NS_strlen(aStr);
+ for (i=0; i<len; i++) {
+ // If string contains CR or LF, it is not safe for LDIF
+ // and MUST be base64 encoded
+ if ((aStr[i] == char16_t('\n')) ||
+ (aStr[i] == char16_t('\r')) ||
+ (!NS_IsAscii(aStr[i])))
+ return false;
+ }
+ return true;
+}
+
+nsresult nsAbManager::AppendProperty(const char *aProperty, const char16_t *aValue, nsACString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+
+ aResult += aProperty;
+
+ // if the string is not safe "as is", base64 encode it
+ if (IsSafeLDIFString(aValue)) {
+ aResult.AppendLiteral(": ");
+ aResult.Append(NS_LossyConvertUTF16toASCII(aValue));
+ }
+ else {
+ char *base64Str = PL_Base64Encode(NS_ConvertUTF16toUTF8(aValue).get(), 0, nullptr);
+ if (!base64Str)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aResult.AppendLiteral(":: ");
+ aResult.Append(nsDependentCString(base64Str));
+ PR_Free(base64Str);
+ }
+
+ return NS_OK;
+}
+
+char *getCString(VObject *vObj)
+{
+ if (VALUE_TYPE(vObj) == VCVT_USTRINGZ)
+ return fakeCString(vObjectUStringZValue(vObj));
+ if (VALUE_TYPE(vObj) == VCVT_STRINGZ)
+ return PL_strdup(vObjectStringZValue(vObj));
+ return NULL;
+}
+
+static void convertNameValue(VObject *vObj, nsIAbCard *aCard)
+{
+ const char *cardPropName = NULL;
+
+ // if the vCard property is not a root property then we need to determine its exact property.
+ // a good example of this is VCTelephoneProp, this prop has four objects underneath it:
+ // fax, work and home and cellular.
+ if (PL_strcasecmp(VCCityProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkCityProperty;
+ else if (PL_strcasecmp(VCTelephoneProp, vObjectName(vObj)) == 0)
+ {
+ if (isAPropertyOf(vObj, VCFaxProp))
+ cardPropName = kFaxProperty;
+ else if (isAPropertyOf(vObj, VCWorkProp))
+ cardPropName = kWorkPhoneProperty;
+ else if (isAPropertyOf(vObj, VCHomeProp))
+ cardPropName = kHomePhoneProperty;
+ else if (isAPropertyOf(vObj, VCCellularProp))
+ cardPropName = kCellularProperty;
+ else if (isAPropertyOf(vObj, VCPagerProp))
+ cardPropName = kPagerProperty;
+ else
+ return;
+ }
+ else if (PL_strcasecmp(VCEmailAddressProp, vObjectName(vObj)) == 0)
+ cardPropName = kPriEmailProperty;
+ else if (PL_strcasecmp(VCFamilyNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kLastNameProperty;
+ else if (PL_strcasecmp(VCFullNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kDisplayNameProperty;
+ else if (PL_strcasecmp(VCGivenNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kFirstNameProperty;
+ else if (PL_strcasecmp(VCOrgNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kCompanyProperty;
+ else if (PL_strcasecmp(VCOrgUnitProp, vObjectName(vObj)) == 0)
+ cardPropName = kDepartmentProperty;
+ else if (PL_strcasecmp(VCPostalCodeProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkZipCodeProperty;
+ else if (PL_strcasecmp(VCRegionProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkStateProperty;
+ else if (PL_strcasecmp(VCStreetAddressProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkAddressProperty;
+ else if (PL_strcasecmp(VCPostalBoxProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkAddress2Property;
+ else if (PL_strcasecmp(VCCountryNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkCountryProperty;
+ else if (PL_strcasecmp(VCTitleProp, vObjectName(vObj)) == 0)
+ cardPropName = kJobTitleProperty;
+ else if (PL_strcasecmp(VCUseHTML, vObjectName(vObj)) == 0)
+ cardPropName = kPreferMailFormatProperty;
+ else if (PL_strcasecmp(VCNoteProp, vObjectName(vObj)) == 0)
+ cardPropName = kNotesProperty;
+ else if (PL_strcasecmp(VCURLProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkWebPageProperty;
+ else
+ return;
+
+ if (!VALUE_TYPE(vObj))
+ return;
+
+ char *cardPropValue = getCString(vObj);
+ if (PL_strcmp(cardPropName, kPreferMailFormatProperty)) {
+ aCard->SetPropertyAsAUTF8String(cardPropName, nsDependentCString(cardPropValue));
+ } else {
+ if (!PL_strcmp(cardPropValue, "TRUE"))
+ aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::html);
+ else if (!PL_strcmp(cardPropValue, "FALSE"))
+ aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::plaintext);
+ else
+ aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::unknown);
+ }
+ PR_FREEIF(cardPropValue);
+ return;
+}
+
+static void convertFromVObject(VObject *vObj, nsIAbCard *aCard)
+{
+ if (vObj)
+ {
+ VObjectIterator t;
+
+ convertNameValue(vObj, aCard);
+
+ initPropIterator(&t, vObj);
+ while (moreIteration(&t))
+ {
+ VObject * nextObject = nextVObject(&t);
+ convertFromVObject(nextObject, aCard);
+ }
+ }
+ return;
+}
+
+NS_IMETHODIMP nsAbManager::EscapedVCardToAbCard(const char *aEscapedVCardStr, nsIAbCard **aCard)
+{
+ NS_ENSURE_ARG_POINTER(aEscapedVCardStr);
+ NS_ENSURE_ARG_POINTER(aCard);
+
+ nsCOMPtr <nsIAbCard> cardFromVCard = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID);
+ if (!cardFromVCard)
+ return NS_ERROR_FAILURE;
+
+ // aEscapedVCardStr will be "" the first time, before you have a vCard
+ if (*aEscapedVCardStr != '\0') {
+ nsCString unescapedData;
+ MsgUnescapeString(nsDependentCString(aEscapedVCardStr), 0, unescapedData);
+
+ VObject *vObj = parse_MIME(unescapedData.get(), unescapedData.Length());
+ if (vObj)
+ {
+ convertFromVObject(vObj, cardFromVCard);
+
+ cleanVObject(vObj);
+ }
+ else
+ NS_WARNING("Parse of vCard failed");
+ }
+
+ NS_IF_ADDREF(*aCard = cardFromVCard);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbManager::Handle(nsICommandLine* aCmdLine)
+{
+ nsresult rv;
+ bool found;
+
+ rv = aCmdLine->HandleFlag(NS_LITERAL_STRING("addressbook"), false, &found);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!found)
+ return NS_OK;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch (do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE);
+
+ nsCOMPtr<mozIDOMWindowProxy> opened;
+ wwatch->OpenWindow(nullptr,
+ "chrome://messenger/content/addressbook/addressbook.xul",
+ "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar",
+ nullptr, getter_AddRefs(opened));
+ aCmdLine->SetPreventDefault(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbManager::GetHelpInfo(nsACString& aResult)
+{
+ aResult.Assign(NS_LITERAL_CSTRING(" -addressbook Open the address book at startup.\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbManager::GenerateUUID(const nsACString &aDirectoryId,
+ const nsACString &aLocalId, nsACString &uuid)
+{
+ uuid.Assign(aDirectoryId);
+ uuid.Append('#');
+ uuid.Append(aLocalId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbManager::ConvertQueryStringToExpression(const nsACString &aQueryString,
+ nsIAbBooleanExpression **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ return nsAbQueryStringToExpression::Convert(aQueryString,
+ _retval);
+}
diff --git a/mailnews/addrbook/src/nsAbManager.h b/mailnews/addrbook/src/nsAbManager.h
new file mode 100644
index 000000000..066fa8a08
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbManager.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsAbManager_h
+#define __nsAbManager_h
+
+#include "nsIAbManager.h"
+#include "nsTObserverArray.h"
+#include "nsCOMPtr.h"
+#include "nsICommandLineHandler.h"
+#include "nsIObserver.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIAbDirFactoryService.h"
+#include "nsIAbDirectory.h"
+
+class nsIAbLDAPAttributeMap;
+
+class nsAbManager : public nsIAbManager,
+ public nsICommandLineHandler,
+ public nsIObserver
+{
+
+public:
+ nsAbManager();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABMANAGER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSICOMMANDLINEHANDLER
+
+ nsresult Init();
+
+private:
+ virtual ~nsAbManager();
+ nsresult GetRootDirectory(nsIAbDirectory **aResult);
+ nsresult ExportDirectoryToDelimitedText(nsIAbDirectory *aDirectory, const char *aDelim,
+ uint32_t aDelimLen, nsIFile *aLocalFile, bool useUTF8);
+ nsresult ExportDirectoryToVCard(nsIAbDirectory *aDirectory, nsIFile *aLocalFile);
+ nsresult ExportDirectoryToLDIF(nsIAbDirectory *aDirectory, nsIFile *aLocalFile);
+ nsresult AppendLDIFForMailList(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult);
+ nsresult AppendDNForCard(const char *aProperty, nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult);
+ nsresult AppendBasicLDIFForCard(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult);
+ nsresult AppendProperty(const char *aProperty, const char16_t *aValue, nsACString &aResult);
+ bool IsSafeLDIFString(const char16_t *aStr);
+
+ struct abListener {
+ nsCOMPtr<nsIAbListener> mListener;
+ uint32_t mNotifyFlags;
+
+ abListener(nsIAbListener *aListener, uint32_t aNotifyFlags)
+ : mListener(aListener), mNotifyFlags(aNotifyFlags) {}
+ abListener(const abListener &aListener)
+ : mListener(aListener.mListener), mNotifyFlags(aListener.mNotifyFlags) {}
+ ~abListener() {}
+
+ int operator==(nsIAbListener* aListener) const {
+ return mListener == aListener;
+ }
+ int operator==(const abListener &aListener) const {
+ return mListener == aListener.mListener;
+ }
+ };
+
+ nsTObserverArray<abListener> mListeners;
+ nsCOMPtr<nsIAbDirectory> mCacheTopLevelAb;
+ nsInterfaceHashtable<nsCStringHashKey, nsIAbDirectory> mAbStore;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbOSXCard.h b/mailnews/addrbook/src/nsAbOSXCard.h
new file mode 100644
index 000000000..51f41c1d7
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXCard.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbOSXCard_h___
+#define nsAbOSXCard_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbCardProperty.h"
+
+#define NS_ABOSXCARD_URI_PREFIX NS_ABOSXCARD_PREFIX "://"
+
+#define NS_IABOSXCARD_IID \
+ { 0xa7e5b697, 0x772d, 0x4fb5, \
+ { 0x81, 0x16, 0x23, 0xb7, 0x5a, 0xac, 0x94, 0x56 } }
+
+class nsIAbOSXCard : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IABOSXCARD_IID)
+
+ virtual nsresult Init(const char *aUri) = 0;
+ virtual nsresult Update(bool aNotify) = 0;
+ virtual nsresult GetURI(nsACString &aURI) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIAbOSXCard, NS_IABOSXCARD_IID)
+
+class nsAbOSXCard : public nsAbCardProperty,
+ public nsIAbOSXCard
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsresult Update(bool aNotify) override;
+ nsresult GetURI(nsACString &aURI) override;
+ nsresult Init(const char *aUri) override;
+ // this is needed so nsAbOSXUtils.mm can get at nsAbCardProperty
+ friend class nsAbOSXUtils;
+private:
+ nsCString mURI;
+
+ virtual ~nsAbOSXCard() {}
+};
+
+#endif // nsAbOSXCard_h___
diff --git a/mailnews/addrbook/src/nsAbOSXCard.mm b/mailnews/addrbook/src/nsAbOSXCard.mm
new file mode 100644
index 000000000..28f978c24
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXCard.mm
@@ -0,0 +1,401 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbOSXCard.h"
+#include "nsAbOSXDirectory.h"
+#include "nsAbOSXUtils.h"
+#include "nsAutoPtr.h"
+#include "nsIAbManager.h"
+#include "nsObjCExceptions.h"
+#include "nsServiceManagerUtils.h"
+
+#include <AddressBook/AddressBook.h>
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbOSXCard,
+ nsAbCardProperty,
+ nsIAbOSXCard)
+
+#ifdef DEBUG
+static ABPropertyType
+GetPropertType(ABRecord *aCard, NSString *aProperty)
+{
+ ABPropertyType propertyType = kABErrorInProperty;
+ if ([aCard isKindOfClass:[ABPerson class]])
+ propertyType = [ABPerson typeOfProperty:aProperty];
+ else if ([aCard isKindOfClass:[ABGroup class]])
+ propertyType = [ABGroup typeOfProperty:aProperty];
+ return propertyType;
+}
+#endif
+
+static void
+SetStringProperty(nsAbOSXCard *aCard, const nsString &aValue,
+ const char *aMemberName, bool aNotify,
+ nsIAbManager *aAbManager)
+{
+ nsString oldValue;
+ nsresult rv = aCard->GetPropertyAsAString(aMemberName, oldValue);
+ if (NS_FAILED(rv))
+ oldValue.Truncate();
+
+ if (!aNotify) {
+ aCard->SetPropertyAsAString(aMemberName, aValue);
+ }
+ else if (!oldValue.Equals(aValue)) {
+ aCard->SetPropertyAsAString(aMemberName, aValue);
+
+ nsISupports *supports = NS_ISUPPORTS_CAST(nsAbCardProperty*, aCard);
+
+ aAbManager->NotifyItemPropertyChanged(supports, aMemberName,
+ oldValue.get(), aValue.get());
+ }
+}
+
+static void
+SetStringProperty(nsAbOSXCard *aCard, NSString *aValue, const char *aMemberName,
+ bool aNotify, nsIAbManager *aAbManager)
+{
+ nsAutoString value;
+ if (aValue)
+ AppendToString(aValue, value);
+
+ SetStringProperty(aCard, value, aMemberName, aNotify, aAbManager);
+}
+
+static void
+MapStringProperty(nsAbOSXCard *aCard, ABRecord *aOSXCard, NSString *aProperty,
+ const char *aMemberName, bool aNotify,
+ nsIAbManager *aAbManager)
+{
+ NS_ASSERTION(aProperty, "This is bad! You asked for an unresolved symbol.");
+ NS_ASSERTION(GetPropertType(aOSXCard, aProperty) == kABStringProperty,
+ "Wrong type!");
+
+ SetStringProperty(aCard, [aOSXCard valueForProperty:aProperty], aMemberName,
+ aNotify, aAbManager);
+}
+
+static ABMutableMultiValue*
+GetMultiValue(ABRecord *aCard, NSString *aProperty)
+{
+ NS_ASSERTION(aProperty, "This is bad! You asked for an unresolved symbol.");
+ NS_ASSERTION(GetPropertType(aCard, aProperty) & kABMultiValueMask,
+ "Wrong type!");
+
+ return [aCard valueForProperty:aProperty];
+}
+
+static void
+MapDate(nsAbOSXCard *aCard, NSDate *aDate, const char *aYearPropName,
+ const char *aMonthPropName, const char *aDayPropName, bool aNotify,
+ nsIAbManager *aAbManager)
+{
+ // XXX Should we pass a format and timezone?
+ NSCalendarDate *date = [aDate dateWithCalendarFormat:nil timeZone:nil];
+
+ nsAutoString value;
+ value.AppendInt(static_cast<int32_t>([date yearOfCommonEra]));
+ SetStringProperty(aCard, value, aYearPropName, aNotify, aAbManager);
+ value.Truncate();
+ value.AppendInt(static_cast<int32_t>([date monthOfYear]));
+ SetStringProperty(aCard, value, aMonthPropName, aNotify, aAbManager);
+ value.Truncate();
+ value.AppendInt(static_cast<int32_t>([date dayOfMonth]));
+ SetStringProperty(aCard, value, aDayPropName, aNotify, aAbManager);
+}
+
+static bool
+MapMultiValue(nsAbOSXCard *aCard, ABRecord *aOSXCard,
+ const nsAbOSXPropertyMap &aMap, bool aNotify,
+ nsIAbManager *aAbManager)
+{
+ ABMultiValue *value = GetMultiValue(aOSXCard, aMap.mOSXProperty);
+ if (value) {
+ unsigned int j;
+ unsigned int count = [value count];
+ for (j = 0; j < count; ++j) {
+ if ([[value labelAtIndex:j] isEqualToString:aMap.mOSXLabel]) {
+ NSString *stringValue = (aMap.mOSXKey)
+ ? [[value valueAtIndex:j] objectForKey:aMap.mOSXKey]
+ : [value valueAtIndex:j];
+
+ SetStringProperty(aCard, stringValue, aMap.mPropertyName, aNotify,
+ aAbManager);
+
+ return true;
+ }
+ }
+ }
+ // String wasn't found, set value of card to empty if it was set previously
+ SetStringProperty(aCard, EmptyString(), aMap.mPropertyName, aNotify,
+ aAbManager);
+
+ return false;
+}
+
+// Maps Address Book's instant messenger name to the corresponding nsIAbCard field name.
+static const char*
+InstantMessengerFieldName(NSString* aInstantMessengerName)
+{
+ if ([aInstantMessengerName isEqualToString:@"AIMInstant"]) {
+ return "_AimScreenName";
+ }
+ if ([aInstantMessengerName isEqualToString:@"GoogleTalkInstant"]) {
+ return "_GoogleTalk";
+ }
+ if ([aInstantMessengerName isEqualToString:@"ICQInstant"]) {
+ return "_ICQ";
+ }
+ if ([aInstantMessengerName isEqualToString:@"JabberInstant"]) {
+ return "_JabberId";
+ }
+ if ([aInstantMessengerName isEqualToString:@"MSNInstant"]) {
+ return "_MSN";
+ }
+ if ([aInstantMessengerName isEqualToString:@"QQInstant"]) {
+ return "_QQ";
+ }
+ if ([aInstantMessengerName isEqualToString:@"SkypeInstant"]) {
+ return "_Skype";
+ }
+ if ([aInstantMessengerName isEqualToString:@"YahooInstant"]) {
+ return "_Yahoo";
+ }
+
+ // Fall back to AIM for everything else.
+ // We don't have nsIAbCard fields for FacebookInstant and GaduGaduInstant.
+ return "_AimScreenName";
+}
+
+nsresult
+nsAbOSXCard::Init(const char *aUri)
+{
+ if (strncmp(aUri, NS_ABOSXCARD_URI_PREFIX,
+ sizeof(NS_ABOSXCARD_URI_PREFIX) - 1) != 0)
+ return NS_ERROR_FAILURE;
+
+ mURI = aUri;
+
+ SetLocalId(nsDependentCString(aUri));
+
+ return Update(false);
+}
+
+nsresult
+nsAbOSXCard::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+nsresult
+nsAbOSXCard::Update(bool aNotify)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
+
+ const char *uid = &((mURI.get())[16]);
+ ABRecord *card = [addressBook recordForUniqueId:[NSString stringWithUTF8String:uid]];
+ NS_ENSURE_TRUE(card, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIAbManager> abManager;
+ nsresult rv;
+ if (aNotify) {
+ abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if ([card isKindOfClass:[ABGroup class]]) {
+ m_IsMailList = true;
+ m_MailListURI.AssignLiteral(NS_ABOSXDIRECTORY_URI_PREFIX);
+ m_MailListURI.Append(uid);
+ MapStringProperty(this, card, kABGroupNameProperty, "DisplayName", aNotify,
+ abManager);
+ MapStringProperty(this, card, kABGroupNameProperty, "LastName", aNotify,
+ abManager);
+
+ return NS_OK;
+ }
+
+ bool foundHome = false, foundWork = false;
+
+ uint32_t i;
+ for (i = 0; i < nsAbOSXUtils::kPropertyMapSize; ++i) {
+ const nsAbOSXPropertyMap &propertyMap = nsAbOSXUtils::kPropertyMap[i];
+ if (!propertyMap.mOSXProperty)
+ continue;
+
+ if (propertyMap.mOSXLabel) {
+ if (MapMultiValue(this, card, propertyMap, aNotify,
+ abManager) && propertyMap.mOSXProperty == kABAddressProperty) {
+ if (propertyMap.mOSXLabel == kABAddressHomeLabel)
+ foundHome = true;
+ else
+ foundWork = true;
+ }
+ }
+ else {
+ MapStringProperty(this, card, propertyMap.mOSXProperty,
+ propertyMap.mPropertyName, aNotify, abManager);
+ }
+ }
+
+ int flags = 0;
+ if (kABPersonFlags)
+ flags = [[card valueForProperty:kABPersonFlags] intValue];
+
+#define SET_STRING(_value, _name, _notify, _session) \
+ SetStringProperty(this, _value, #_name, _notify, _session)
+
+ // If kABShowAsCompany is set we use the company name as display name.
+ if (kABPersonFlags && (flags & kABShowAsCompany)) {
+ nsString company;
+ nsresult rv = GetPropertyAsAString(kCompanyProperty, company);
+ if (NS_FAILED(rv))
+ company.Truncate();
+ SET_STRING(company, DisplayName, aNotify, abManager);
+ }
+ else {
+ // Use the order used in the OS X address book to set DisplayName.
+ int order = kABPersonFlags && (flags & kABNameOrderingMask);
+ if (kABPersonFlags && (order == kABDefaultNameOrdering)) {
+ order = [addressBook defaultNameOrdering];
+ }
+
+ nsAutoString displayName, tempName;
+ if (kABPersonFlags && (order == kABFirstNameFirst)) {
+ GetFirstName(tempName);
+ displayName.Append(tempName);
+
+ GetLastName(tempName);
+
+ // Only append a space if the last name and the first name are not empty
+ if (!tempName.IsEmpty() && !displayName.IsEmpty())
+ displayName.Append(' ');
+
+ displayName.Append(tempName);
+ }
+ else {
+ GetLastName(tempName);
+ displayName.Append(tempName);
+
+ GetFirstName(tempName);
+
+ // Only append a space if the last name and the first name are not empty
+ if (!tempName.IsEmpty() && !displayName.IsEmpty())
+ displayName.Append(' ');
+
+ displayName.Append(tempName);
+ }
+ SET_STRING(displayName, DisplayName, aNotify, abManager);
+ }
+
+ ABMultiValue *value = GetMultiValue(card, kABEmailProperty);
+ if (value) {
+ unsigned int count = [value count];
+ if (count > 0) {
+ unsigned int j = [value indexForIdentifier:[value primaryIdentifier]];
+
+ if (j < count)
+ SET_STRING([value valueAtIndex:j], PrimaryEmail, aNotify,
+ abManager);
+
+ // If j is 0 (first in the list) we want the second in the list
+ // (index 1), if j is anything else we want the first in the list
+ // (index 0).
+ j = (j == 0);
+ if (j < count)
+ SET_STRING([value valueAtIndex:j], SecondEmail, aNotify,
+ abManager);
+ }
+ }
+
+ // We map the first home address we can find and the first work address
+ // we can find. If we find none, we map the primary address to the home
+ // address.
+ if (!foundHome && !foundWork) {
+ value = GetMultiValue(card, kABAddressProperty);
+ if (value) {
+ unsigned int count = [value count];
+ unsigned int j = [value indexForIdentifier:[value primaryIdentifier]];
+
+ if (j < count) {
+ NSDictionary *address = [value valueAtIndex:j];
+ if (address) {
+ SET_STRING([address objectForKey:kABAddressStreetKey],
+ HomeAddress, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressCityKey],
+ HomeCity, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressStateKey],
+ HomeState, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressZIPKey],
+ HomeZipCode, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressCountryKey],
+ HomeCountry, aNotify, abManager);
+ }
+ }
+ }
+ }
+ // This was kABAIMInstantProperty previously, but it was deprecated in OS X 10.7.
+ value = GetMultiValue(card, kABInstantMessageProperty);
+ if (value) {
+ unsigned int count = [value count];
+ for (size_t i = 0; i < count; i++) {
+ id imValue = [value valueAtIndex:i];
+ // Depending on the macOS version, imValue can be an NSString or an NSDictionary.
+ if ([imValue isKindOfClass:[NSString class]]) {
+ if (i == [value indexForIdentifier:[value primaryIdentifier]]) {
+ SET_STRING(imValue, _AimScreenName, aNotify, abManager);
+ }
+ } else if ([imValue isKindOfClass:[NSDictionary class]]) {
+ NSString* instantMessageService = [imValue objectForKey:@"InstantMessageService"];
+ const char* fieldName = InstantMessengerFieldName(instantMessageService);
+ NSString* userName = [imValue objectForKey:@"InstantMessageUsername"];
+ SetStringProperty(this, userName, fieldName, aNotify, abManager);
+ }
+ }
+ }
+
+#define MAP_DATE(_date, _name, _notify, _session) \
+ MapDate(this, _date, #_name"Year", #_name"Month", #_name"Day", _notify, \
+ _session)
+
+ NSDate *date = [card valueForProperty:kABBirthdayProperty];
+ if (date)
+ MAP_DATE(date, Birth, aNotify, abManager);
+
+ if (kABOtherDatesProperty) {
+ value = GetMultiValue(card, kABOtherDatesProperty);
+ if (value) {
+ unsigned int j, count = [value count];
+ for (j = 0; j < count; ++j) {
+ if ([[value labelAtIndex:j] isEqualToString:kABAnniversaryLabel]) {
+ date = [value valueAtIndex:j];
+ if (date) {
+ MAP_DATE(date, Anniversary, aNotify, abManager);
+
+ break;
+ }
+ }
+ }
+ }
+ }
+#undef MAP_DATE
+#undef SET_STRING
+
+ date = [card valueForProperty:kABModificationDateProperty];
+ if (date)
+ SetPropertyAsUint32("LastModifiedDate",
+ uint32_t([date timeIntervalSince1970]));
+ // XXX No way to notify about this?
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/mailnews/addrbook/src/nsAbOSXDirFactory.cpp b/mailnews/addrbook/src/nsAbOSXDirFactory.cpp
new file mode 100644
index 000000000..e9f2d7d3b
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXDirFactory.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbOSXDirFactory.h"
+#include "nsAbBaseCID.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbManager.h"
+#include "nsStringGlue.h"
+#include "nsServiceManagerUtils.h"
+#include "nsAbOSXDirectory.h"
+
+NS_IMPL_ISUPPORTS(nsAbOSXDirFactory, nsIAbDirFactory)
+
+NS_IMETHODIMP
+nsAbOSXDirFactory::GetDirectories(const nsAString &aDirName,
+ const nsACString &aURI,
+ const nsACString &aPrefName,
+ nsISimpleEnumerator **aDirectories)
+{
+ NS_ENSURE_ARG_POINTER(aDirectories);
+
+ *aDirectories = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX "/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory(do_QueryInterface(directory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = osxDirectory->AssertChildNodes();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewSingletonEnumerator(aDirectories, osxDirectory);
+}
+
+// No actual deletion, since you cannot create the address books from Mozilla.
+NS_IMETHODIMP
+nsAbOSXDirFactory::DeleteDirectory(nsIAbDirectory *aDirectory)
+{
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbOSXDirFactory.h b/mailnews/addrbook/src/nsAbOSXDirFactory.h
new file mode 100644
index 000000000..4c86211d3
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXDirFactory.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbOSXDirFactory_h___
+#define nsAbOSXDirFactory_h___
+
+#include "nsIAbDirFactory.h"
+
+class nsAbOSXDirFactory final : public nsIAbDirFactory
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORY
+
+private:
+ ~nsAbOSXDirFactory() {}
+};
+
+#endif // nsAbOSXDirFactory_h___
diff --git a/mailnews/addrbook/src/nsAbOSXDirectory.h b/mailnews/addrbook/src/nsAbOSXDirectory.h
new file mode 100644
index 000000000..7e3fad96c
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXDirectory.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbOSXDirectory_h___
+#define nsAbOSXDirectory_h___
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include "nsAbBaseCID.h"
+#include "nsAbDirProperty.h"
+#include "nsIAbDirectoryQuery.h"
+#include "nsIAbDirectorySearch.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsIMutableArray.h"
+#include "nsInterfaceHashtable.h"
+#include "nsAbOSXCard.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+class nsIAbManager;
+class nsIAbBooleanExpression;
+
+#define NS_ABOSXDIRECTORY_URI_PREFIX NS_ABOSXDIRECTORY_PREFIX "://"
+
+#define NS_IABOSXDIRECTORY_IID \
+{ 0x87ee4bd9, 0x8552, 0x498f, \
+ { 0x80, 0x85, 0x34, 0xf0, 0x2a, 0xbb, 0x56, 0x16 } }
+
+class nsIAbOSXDirectory : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IABOSXDIRECTORY_IID)
+
+ virtual nsresult AssertChildNodes() = 0;
+ virtual nsresult Update() = 0;
+ virtual nsresult AssertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory) = 0;
+ virtual nsresult AssertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard) = 0;
+ virtual nsresult UnassertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard,
+ nsIMutableArray *aCardList) = 0;
+ virtual nsresult UnassertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory) = 0;
+ virtual nsresult DeleteUid(const nsACString &aUid) = 0;
+ virtual nsresult GetURI(nsACString &aURI) = 0;
+ virtual nsresult Init(const char *aUri) = 0;
+ virtual nsresult GetCardByUri(const nsACString &aUri, nsIAbOSXCard **aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIAbOSXDirectory, NS_IABOSXDIRECTORY_IID)
+
+class nsAbOSXDirectory final : public nsAbDirProperty,
+public nsIAbDirSearchListener,
+public nsIAbOSXDirectory
+{
+public:
+ nsAbOSXDirectory();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIABDIRSEARCHLISTENER
+
+ // nsIAbOSXDirectory method
+ NS_IMETHOD Init(const char *aUri) override;
+
+ // nsAbDirProperty methods
+ NS_IMETHOD GetReadOnly(bool *aReadOnly) override;
+ NS_IMETHOD GetChildCards(nsISimpleEnumerator **aCards) override;
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator **aNodes) override;
+ NS_IMETHOD GetIsQuery(bool *aResult) override;
+ NS_IMETHOD HasCard(nsIAbCard *aCard, bool *aHasCard) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory *aDirectory, bool *aHasDirectory) override;
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+ NS_IMETHOD GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsIAbCard **aResult) override;
+ NS_IMETHOD GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool aCaseSensitive,
+ nsISimpleEnumerator **aResult) override;
+ NS_IMETHOD CardForEmailAddress(const nsACString &aEmailAddress,
+ nsIAbCard **aResult) override;
+
+ // nsIAbOSXDirectory
+ nsresult AssertChildNodes() override;
+ nsresult AssertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory) override;
+ nsresult AssertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard) override;
+ nsresult UnassertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard,
+ nsIMutableArray *aCardList) override;
+ nsresult UnassertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory) override;
+
+ nsresult Update() override;
+
+ nsresult DeleteUid(const nsACString &aUid) override;
+
+ nsresult GetCardByUri(const nsACString &aUri, nsIAbOSXCard **aResult) override;
+
+ nsresult GetRootOSXDirectory(nsIAbOSXDirectory **aResult);
+
+private:
+ ~nsAbOSXDirectory();
+ nsresult FallbackSearch(nsIAbBooleanExpression *aExpression,
+ nsISimpleEnumerator **aCards);
+
+ // This is a list of nsIAbCards, kept separate from m_AddressList because:
+ // - nsIAbDirectory items that are mailing lists, must keep a list of
+ // nsIAbCards in m_AddressList, however
+ // - nsIAbDirectory items that are address books, must keep a list of
+ // nsIAbDirectory (i.e. mailing lists) in m_AddressList, AND no nsIAbCards.
+ //
+ // This wasn't too bad for mork, as that just gets a list from its database,
+ // but because we store our own copy of the list, we must store a separate
+ // list of nsIAbCards here. nsIMutableArray is used, because then it is
+ // interchangeable with m_AddressList.
+ nsCOMPtr<nsIMutableArray> mCardList;
+ nsInterfaceHashtable<nsCStringHashKey, nsIAbOSXCard> mCardStore;
+ nsCOMPtr<nsIAbOSXDirectory> mCacheTopLevelOSXAb;
+};
+
+#endif // nsAbOSXDirectory_h___
diff --git a/mailnews/addrbook/src/nsAbOSXDirectory.mm b/mailnews/addrbook/src/nsAbOSXDirectory.mm
new file mode 100644
index 000000000..e195fe3a7
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXDirectory.mm
@@ -0,0 +1,1374 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbOSXDirectory.h"
+#include "nsAbOSXCard.h"
+#include "nsAbOSXUtils.h"
+#include "nsAbQueryStringToExpression.h"
+#include "nsArrayEnumerator.h"
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIAbDirectoryQueryProxy.h"
+#include "nsIAbManager.h"
+#include "nsObjCExceptions.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIAbBooleanExpression.h"
+#include "nsComponentManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+
+#include <AddressBook/AddressBook.h>
+
+#define kABDeletedRecords (kABDeletedRecords? kABDeletedRecords : @"ABDeletedRecords")
+#define kABUpdatedRecords (kABUpdatedRecords ? kABUpdatedRecords : @"ABUpdatedRecords")
+#define kABInsertedRecords (kABInsertedRecords ? kABInsertedRecords : @"ABInsertedRecords")
+
+static nsresult
+GetOrCreateGroup(NSString *aUid, nsIAbDirectory **aResult)
+{
+ NS_ASSERTION(aUid, "No UID for group!.");
+
+ nsAutoCString uri(NS_ABOSXDIRECTORY_URI_PREFIX);
+ AppendToCString(aUid, uri);
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = directory);
+ return NS_OK;
+}
+
+static nsresult
+GetCard(ABRecord *aRecord, nsIAbCard **aResult, nsIAbOSXDirectory *osxDirectory)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString *uid = [aRecord uniqueId];
+ NS_ASSERTION(uid, "No UID for card!.");
+ if (!uid)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString uri(NS_ABOSXCARD_URI_PREFIX);
+ AppendToCString(uid, uri);
+ nsCOMPtr<nsIAbOSXCard> osxCard;
+ nsresult rv = osxDirectory->GetCardByUri(uri, getter_AddRefs(osxCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card = do_QueryInterface(osxCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = card);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+static nsresult
+CreateCard(ABRecord *aRecord, nsIAbCard **aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString *uid = [aRecord uniqueId];
+ NS_ASSERTION(uid, "No UID for card!.");
+ if (!uid)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_CreateInstance(NS_ABOSXCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri(NS_ABOSXCARD_URI_PREFIX);
+ AppendToCString(uid, uri);
+
+ rv = osxCard->Init(uri.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card = do_QueryInterface(osxCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = card);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+static nsresult
+Sync(NSString *aUid)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
+ ABRecord *card = [addressBook recordForUniqueId:aUid];
+ if ([card isKindOfClass:[ABGroup class]])
+ {
+ nsCOMPtr<nsIAbDirectory> directory;
+ GetOrCreateGroup(aUid, getter_AddRefs(directory));
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory);
+
+ if (osxDirectory) {
+ osxDirectory->Update();
+ }
+ }
+ else {
+ nsCOMPtr<nsIAbCard> abCard;
+ nsresult rv;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetCard(card, getter_AddRefs(abCard), osxDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(abCard);
+ osxCard->Update(true);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+@interface ABChangedMonitor : NSObject
+-(void)ABChanged:(NSNotification *)aNotification;
+@end
+
+@implementation ABChangedMonitor
+-(void)ABChanged:(NSNotification *)aNotification
+{
+ NSDictionary *changes = [aNotification userInfo];
+
+ nsresult rv;
+ NSArray *inserted = [changes objectForKey:kABInsertedRecords];
+
+ if (inserted) {
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ unsigned int i, count = [inserted count];
+ for (i = 0; i < count; ++i) {
+ ABAddressBook *addressBook =
+ [ABAddressBook sharedAddressBook];
+ ABRecord *card =
+ [addressBook recordForUniqueId:[inserted objectAtIndex:i]];
+ if ([card isKindOfClass:[ABGroup class]]) {
+ nsCOMPtr<nsIAbDirectory> directory;
+ GetOrCreateGroup([inserted objectAtIndex:i],
+ getter_AddRefs(directory));
+
+ rv = osxDirectory->AssertDirectory(abManager, directory);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ else {
+ nsCOMPtr<nsIAbCard> abCard;
+ // Construct a card
+ nsresult rv = CreateCard(card, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = osxDirectory->AssertCard(abManager, abCard);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+ }
+
+ NSArray *updated = [changes objectForKey:kABUpdatedRecords];
+ if (updated) {
+ unsigned int i, count = [updated count];
+ for (i = 0; i < count; ++i) {
+ NSString *uid = [updated objectAtIndex:i];
+ Sync(uid);
+ }
+ }
+
+ NSArray *deleted = [changes objectForKey:kABDeletedRecords];
+ if (deleted) {
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ unsigned int i, count = [deleted count];
+ for (i = 0; i < count; ++i) {
+ NSString *deletedUid = [deleted objectAtIndex:i];
+
+ nsAutoCString uid;
+ AppendToCString(deletedUid, uid);
+
+ rv = osxDirectory->DeleteUid(uid);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+
+ if (!inserted && !updated && !deleted) {
+ // XXX This is supposed to mean "everything was updated", but we get
+ // this whenever something has changed, so not sure what to do.
+ }
+}
+@end
+
+static nsresult
+MapConditionString(nsIAbBooleanConditionString *aCondition, bool aNegate,
+ bool &aCanHandle, ABSearchElement **aResult)
+{
+ aCanHandle = false;
+
+ nsAbBooleanConditionType conditionType = 0;
+ nsresult rv = aCondition->GetCondition(&conditionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ABSearchComparison comparison;
+ switch (conditionType) {
+ case nsIAbBooleanConditionTypes::Contains:
+ {
+ if (!aNegate) {
+ comparison = kABContainsSubString;
+ aCanHandle = true;
+ }
+ break;
+ }
+ case nsIAbBooleanConditionTypes::DoesNotContain:
+ {
+ if (aNegate) {
+ comparison = kABContainsSubString;
+ aCanHandle = true;
+ }
+ break;
+ }
+ case nsIAbBooleanConditionTypes::Is:
+ {
+ comparison = aNegate ? kABNotEqual : kABEqual;
+ aCanHandle = true;
+ break;
+ }
+ case nsIAbBooleanConditionTypes::IsNot:
+ {
+ comparison = aNegate ? kABEqual : kABNotEqual;
+ aCanHandle = true;
+ break;
+ }
+ case nsIAbBooleanConditionTypes::BeginsWith:
+ {
+ if (!aNegate) {
+ comparison = kABPrefixMatch;
+ aCanHandle = true;
+ }
+ break;
+ }
+ case nsIAbBooleanConditionTypes::EndsWith:
+ {
+ //comparison = kABSuffixMatch;
+ break;
+ }
+ case nsIAbBooleanConditionTypes::LessThan:
+ {
+ comparison = aNegate ? kABGreaterThanOrEqual : kABLessThan;
+ aCanHandle = true;
+ break;
+ }
+ case nsIAbBooleanConditionTypes::GreaterThan:
+ {
+ comparison = aNegate ? kABLessThanOrEqual : kABGreaterThan;
+ aCanHandle = true;
+ break;
+ }
+ }
+
+ if (!aCanHandle)
+ return NS_OK;
+
+ nsCString name;
+ rv = aCondition->GetName(getter_Copies(name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString value;
+ rv = aCondition->GetValue(getter_Copies(value));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t length = value.Length();
+
+ uint32_t i;
+ for (i = 0; i < nsAbOSXUtils::kPropertyMapSize; ++i) {
+ if (name.Equals(nsAbOSXUtils::kPropertyMap[i].mPropertyName)) {
+ *aResult =
+ [ABPerson searchElementForProperty:nsAbOSXUtils::kPropertyMap[i].mOSXProperty
+ label:nsAbOSXUtils::kPropertyMap[i].mOSXLabel
+ key:nsAbOSXUtils::kPropertyMap[i].mOSXKey
+ value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length]
+ comparison:comparison];
+
+ return NS_OK;
+ }
+ }
+
+ if (name.EqualsLiteral("DisplayName") && comparison == kABContainsSubString) {
+ ABSearchElement *first =
+ [ABPerson searchElementForProperty:kABFirstNameProperty
+ label:nil
+ key:nil
+ value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length]
+ comparison:comparison];
+ ABSearchElement *second =
+ [ABPerson searchElementForProperty:kABLastNameProperty
+ label:nil
+ key:nil
+ value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length]
+ comparison:comparison];
+ ABSearchElement *third =
+ [ABGroup searchElementForProperty:kABGroupNameProperty
+ label:nil
+ key:nil
+ value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length]
+ comparison:comparison];
+
+ *aResult = [ABSearchElement searchElementForConjunction:kABSearchOr children:[NSArray arrayWithObjects:first, second, third, nil]];
+
+ return NS_OK;
+ }
+
+ aCanHandle = false;
+
+ return NS_OK;
+}
+
+static nsresult
+BuildSearchElements(nsIAbBooleanExpression *aExpression,
+ bool &aCanHandle,
+ ABSearchElement **aResult)
+{
+ aCanHandle = true;
+
+ nsCOMPtr<nsIArray> expressions;
+ nsresult rv = aExpression->GetExpressions(getter_AddRefs(expressions));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAbBooleanOperationType operation;
+ rv = aExpression->GetOperation(&operation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ rv = expressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(count > 1 && operation != nsIAbBooleanOperationTypes::NOT,
+ "This doesn't make sense!");
+
+ NSMutableArray *array = nullptr;
+ if (count > 1)
+ array = [[NSMutableArray alloc] init];
+
+ uint32_t i;
+ nsCOMPtr<nsIAbBooleanConditionString> condition;
+ nsCOMPtr<nsIAbBooleanExpression> subExpression;
+ for (i = 0; i < count; ++i) {
+ ABSearchElement *element = nullptr;
+
+ condition = do_QueryElementAt(expressions, i);
+ if (condition) {
+ rv = MapConditionString(condition, operation == nsIAbBooleanOperationTypes::NOT, aCanHandle, &element);
+ if (NS_FAILED(rv))
+ break;
+ }
+ else {
+ subExpression = do_QueryElementAt(expressions, i);
+ if (subExpression) {
+ rv = BuildSearchElements(subExpression, aCanHandle, &element);
+ if (NS_FAILED(rv))
+ break;
+ }
+ }
+
+ if (!aCanHandle) {
+ // remember to free the array when returning early
+ [array release];
+ return NS_OK;
+ }
+
+ if (element) {
+ if (array)
+ [array addObject:element];
+ else
+ *aResult = element;
+ }
+ }
+
+ if (array) {
+ if (NS_SUCCEEDED(rv)) {
+ ABSearchConjunction conjunction = operation == nsIAbBooleanOperationTypes::AND ? kABSearchAnd : kABSearchOr;
+ *aResult = [ABSearchElement searchElementForConjunction:conjunction children:array];
+ }
+ [array release];
+ }
+
+ return rv;
+}
+
+static bool
+Search(nsIAbBooleanExpression *aExpression, NSArray **aResult)
+{
+ bool canHandle = false;
+ ABSearchElement *searchElement;
+ nsresult rv = BuildSearchElements(aExpression, canHandle, &searchElement);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (canHandle)
+ *aResult = [[ABAddressBook sharedAddressBook] recordsMatchingSearchElement:searchElement];
+
+ return canHandle;
+}
+
+static uint32_t sObserverCount = 0;
+static ABChangedMonitor *sObserver = nullptr;
+
+nsAbOSXDirectory::nsAbOSXDirectory()
+{
+}
+
+nsAbOSXDirectory::~nsAbOSXDirectory()
+{
+ if (--sObserverCount == 0) {
+ [[NSNotificationCenter defaultCenter] removeObserver:sObserver];
+ [sObserver release];
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbOSXDirectory,
+ nsAbDirProperty,
+ nsIAbOSXDirectory,
+ nsIAbDirSearchListener)
+
+NS_IMETHODIMP
+nsAbOSXDirectory::Init(const char *aUri)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ rv = nsAbDirProperty::Init(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
+ if (sObserverCount == 0) {
+ sObserver = [[ABChangedMonitor alloc] init];
+ [[NSNotificationCenter defaultCenter] addObserver:(ABChangedMonitor*)sObserver
+ selector:@selector(ABChanged:)
+ name:kABDatabaseChangedExternallyNotification
+ object:nil];
+ }
+ ++sObserverCount;
+
+ NSArray *cards;
+ nsCOMPtr<nsIMutableArray> cardList;
+ bool isRootOSXDirectory = false;
+
+ if (!mIsQueryURI && mURINoQuery.Length() <= sizeof(NS_ABOSXDIRECTORY_URI_PREFIX))
+ isRootOSXDirectory = true;
+
+ if (mIsQueryURI || isRootOSXDirectory)
+ {
+ m_DirPrefId.AssignLiteral("ldap_2.servers.osx");
+
+ cards = [[addressBook people] arrayByAddingObjectsFromArray:[addressBook groups]];
+ if (!mCardList)
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ else
+ rv = mCardList->Clear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ cardList = mCardList;
+ }
+ else
+ {
+ nsAutoCString uid(Substring(mURINoQuery, sizeof(NS_ABOSXDIRECTORY_URI_PREFIX) - 1));
+ ABRecord *card = [addressBook recordForUniqueId:[NSString stringWithUTF8String:uid.get()]];
+ NS_ASSERTION([card isKindOfClass:[ABGroup class]], "Huh.");
+
+ m_IsMailList = true;
+ AppendToString([card valueForProperty:kABGroupNameProperty], m_ListDirName);
+
+ ABGroup *group = (ABGroup*)[addressBook recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURINoQuery, 21)).get()]];
+ cards = [[group members] arrayByAddingObjectsFromArray:[group subgroups]];
+
+ if (!m_AddressList)
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ else
+ rv = m_AddressList->Clear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ cardList = m_AddressList;
+ }
+
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+
+ unsigned int nbCards = [cards count];
+ nsCOMPtr<nsIAbCard> card;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory;
+ if (!isRootOSXDirectory)
+ {
+ rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (unsigned int i = 0; i < nbCards; ++i)
+ {
+ // If we're a Group, it's likely that the cards we're going
+ // to create were already created in the root nsAbOSXDirectory,
+ if (!isRootOSXDirectory)
+ rv = GetCard([cards objectAtIndex:i], getter_AddRefs(card),
+ rootOSXDirectory);
+ else
+ {
+ // If we're not a Group, that means we're the root nsAbOSXDirectory,
+ // which means we have to create the cards from scratch.
+ rv = CreateCard([cards objectAtIndex:i], getter_AddRefs(card));
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we're not a query directory, we're going to want to
+ // tell the AB Manager that we've added some cards so that they
+ // show up in the address book views.
+ if (!mIsQueryURI)
+ AssertCard(abManager, card);
+
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetReadOnly(bool *aReadOnly)
+{
+ NS_ENSURE_ARG_POINTER(aReadOnly);
+
+ *aReadOnly = true;
+ return NS_OK;
+}
+
+static bool
+CheckRedundantCards(nsIAbManager *aManager, nsIAbDirectory *aDirectory,
+ nsIAbCard *aCard, NSMutableArray *aCardList)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(aCard, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString uri;
+ rv = osxCard->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, false);
+ NSString *uid = [NSString stringWithUTF8String:(uri.get() + 21)];
+
+ unsigned int i, count = [aCardList count];
+ for (i = 0; i < count; ++i) {
+ if ([[[aCardList objectAtIndex:i] uniqueId] isEqualToString:uid]) {
+ [aCardList removeObjectAtIndex:i];
+ break;
+ }
+ }
+
+ if (i == count) {
+ aManager->NotifyDirectoryItemDeleted(aDirectory, aCard);
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsAbOSXDirectory::GetRootOSXDirectory(nsIAbOSXDirectory **aResult)
+{
+ if (!mCacheTopLevelOSXAb)
+ {
+ // Attempt to get card from the toplevel directories
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCacheTopLevelOSXAb = osxDirectory;
+ }
+
+ NS_IF_ADDREF(*aResult = mCacheTopLevelOSXAb);
+ return NS_OK;
+}
+
+nsresult
+nsAbOSXDirectory::Update()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mIsQueryURI) {
+ return NS_OK;
+ }
+
+ ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
+ // Due to the horrible way the address book code works wrt mailing lists
+ // we have to use a different list depending on what we are. This pointer
+ // holds a reference to that list.
+ nsIMutableArray* cardList;
+ NSArray *groups, *cards;
+ if (m_IsMailList) {
+ ABGroup *group = (ABGroup*)[addressBook recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURINoQuery, 21)).get()]];
+ groups = nil;
+ cards = [[group members] arrayByAddingObjectsFromArray:[group subgroups]];
+
+ if (!m_AddressList)
+ {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // For mailing lists, store the cards in m_AddressList
+ cardList = m_AddressList;
+ }
+ else {
+ groups = [addressBook groups];
+ cards = [[addressBook people] arrayByAddingObjectsFromArray:groups];
+
+ if (!mCardList)
+ {
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // For directories, store the cards in mCardList
+ cardList = mCardList;
+ }
+
+ NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:cards];
+ uint32_t addressCount;
+ rv = cardList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (addressCount--)
+ {
+ nsCOMPtr<nsIAbCard> card(do_QueryElementAt(cardList, addressCount, &rv));
+ if (NS_FAILED(rv))
+ break;
+
+ if (CheckRedundantCards(abManager, this, card, mutableArray))
+ cardList->RemoveElementAt(addressCount);
+ }
+
+ NSEnumerator *enumerator = [mutableArray objectEnumerator];
+ ABRecord *card;
+ nsCOMPtr<nsIAbCard> abCard;
+ nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory;
+ rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while ((card = [enumerator nextObject]))
+ {
+ rv = GetCard(card, getter_AddRefs(abCard), rootOSXDirectory);
+ if (NS_FAILED(rv))
+ rv = CreateCard(card, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ AssertCard(abManager, abCard);
+ }
+
+ card = (ABRecord*)[addressBook recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURINoQuery, 21)).get()]];
+ NSString * stringValue = [card valueForProperty:kABGroupNameProperty];
+ if (![stringValue isEqualToString:WrapString(m_ListDirName)])
+ {
+ nsAutoString oldValue(m_ListDirName);
+ AssignToString(stringValue, m_ListDirName);
+ nsCOMPtr<nsISupports> supports = do_QueryInterface(static_cast<nsIAbDirectory *>(this), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ abManager->NotifyItemPropertyChanged(supports, "DirName",
+ oldValue.get(), m_ListDirName.get());
+ }
+
+ if (groups)
+ {
+ mutableArray = [NSMutableArray arrayWithArray:groups];
+ nsCOMPtr<nsIAbDirectory> directory;
+ // It is ok to use m_AddressList here as only top-level directories have
+ // groups, and they will be in m_AddressList
+ if (m_AddressList)
+ {
+ rv = m_AddressList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (addressCount--)
+ {
+ directory = do_QueryElementAt(m_AddressList, addressCount, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsAutoCString uri;
+ directory->GetURI(uri);
+ uri.Cut(0, 21);
+ NSString *uid = [NSString stringWithUTF8String:uri.get()];
+
+ unsigned int j, arrayCount = [mutableArray count];
+ for (j = 0; j < arrayCount; ++j) {
+ if ([[[mutableArray objectAtIndex:j] uniqueId] isEqualToString:uid]) {
+ [mutableArray removeObjectAtIndex:j];
+ break;
+ }
+ }
+
+ if (j == arrayCount) {
+ UnassertDirectory(abManager, directory);
+ }
+ }
+ }
+
+ enumerator = [mutableArray objectEnumerator];
+ while ((card = [enumerator nextObject])) {
+ rv = GetOrCreateGroup([card uniqueId], getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AssertDirectory(abManager, directory);
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsAbOSXDirectory::AssertChildNodes()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Queries and mailing lists can't have childnodes.
+ if (mIsQueryURI || m_IsMailList) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NSArray *groups = [[ABAddressBook sharedAddressBook] groups];
+
+ unsigned int i, count = [groups count];
+
+ if (count > 0 && !m_AddressList) {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ for (i = 0; i < count; ++i) {
+ rv = GetOrCreateGroup([[groups objectAtIndex:i] uniqueId],
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AssertDirectory(abManager, directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsAbOSXDirectory::AssertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory)
+{
+ uint32_t pos;
+ if (m_AddressList &&
+ NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ // We already have this directory, so no point in adding it again.
+ return NS_OK;
+
+ nsresult rv;
+ if (!m_AddressList) {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = m_AddressList->AppendElement(aDirectory, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aManager->NotifyDirectoryItemAdded(this, aDirectory);
+}
+
+nsresult
+nsAbOSXDirectory::AssertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard)
+{
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+ aCard->SetDirectoryId(ourUuid);
+
+ nsresult rv = m_IsMailList ? m_AddressList->AppendElement(aCard, false) :
+ mCardList->AppendElement(aCard, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the card's URI and add it to our card store
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(aCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString uri;
+ rv = osxCard->GetURI(uri);
+
+ nsCOMPtr<nsIAbOSXCard> retrievedCard;
+ if (!mCardStore.Get(uri, getter_AddRefs(retrievedCard)))
+ mCardStore.Put(uri, osxCard);
+
+ return aManager->NotifyDirectoryItemAdded(this, aCard);
+}
+
+nsresult
+nsAbOSXDirectory::UnassertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard,
+ nsIMutableArray *aCardList)
+{
+ nsresult rv;
+ uint32_t pos;
+
+ if (NS_SUCCEEDED(aCardList->IndexOf(0, aCard, &pos)))
+ rv = aCardList->RemoveElementAt(pos);
+
+ return aManager->NotifyDirectoryItemDeleted(this, aCard);
+}
+
+nsresult
+nsAbOSXDirectory::UnassertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory)
+{
+ NS_ENSURE_TRUE(m_AddressList, NS_ERROR_NULL_POINTER);
+
+ uint32_t pos;
+ if (NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ {
+ nsresult rv = m_AddressList->RemoveElementAt(pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return aManager->NotifyDirectoryItemDeleted(this, aDirectory);
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetChildNodes(nsISimpleEnumerator **aNodes)
+{
+ NS_ENSURE_ARG_POINTER(aNodes);
+
+ // Queries don't have childnodes.
+ if (mIsQueryURI || m_IsMailList || !m_AddressList)
+ return NS_NewEmptyEnumerator(aNodes);
+
+ return NS_NewArrayEnumerator(aNodes, m_AddressList);
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetChildCards(nsISimpleEnumerator **aCards)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_ARG_POINTER(aCards);
+
+ nsresult rv;
+ NSArray *cards;
+ if (mIsQueryURI)
+ {
+ nsCOMPtr<nsIAbBooleanExpression> expression;
+ rv = nsAbQueryStringToExpression::Convert(mQueryString,
+ getter_AddRefs(expression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool canHandle = !m_IsMailList && Search(expression, &cards);
+ if (!canHandle)
+ return FallbackSearch(expression, aCards);
+
+ if (!mCardList)
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ else
+ mCardList->Clear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The uuid for initializing cards
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+
+ // Fill the results array and update the card list
+ unsigned int nbCards = [cards count];
+
+ unsigned int i;
+ nsCOMPtr<nsIAbCard> card;
+ nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory;
+ rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < nbCards; ++i)
+ {
+ rv = GetCard([cards objectAtIndex:i], getter_AddRefs(card),
+ rootOSXDirectory);
+
+ if (NS_FAILED(rv))
+ rv = CreateCard([cards objectAtIndex:i],
+ getter_AddRefs(card));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ card->SetDirectoryId(ourUuid);
+
+ mCardList->AppendElement(card, false);
+ }
+
+ return NS_NewArrayEnumerator(aCards, mCardList);
+ }
+
+ // Not a search, so just return the appropriate list of items.
+ return m_IsMailList ? NS_NewArrayEnumerator(aCards, m_AddressList) :
+ NS_NewArrayEnumerator(aCards, mCardList);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mIsQueryURI;
+ return NS_OK;
+}
+
+/* Recursive method that searches for a child card by URI. If it cannot find
+ * it within this directory, it checks all subfolders.
+ */
+NS_IMETHODIMP
+nsAbOSXDirectory::GetCardByUri(const nsACString &aUri, nsIAbOSXCard **aResult)
+{
+ nsCOMPtr<nsIAbOSXCard> osxCard;
+
+ // Base Case
+ if (mCardStore.Get(aUri, getter_AddRefs(osxCard)))
+ {
+ NS_IF_ADDREF(*aResult = osxCard);
+ return NS_OK;
+ }
+ // Search children
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = this->GetChildNodes(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> item;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> childDirectory;
+ childDirectory = do_QueryInterface(item, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = childDirectory->GetCardByUri(aUri, getter_AddRefs(osxCard));
+ if (NS_SUCCEEDED(rv))
+ {
+ NS_IF_ADDREF(*aResult = osxCard);
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool aCaseSensitive,
+ nsIAbCard **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (aValue.IsEmpty())
+ return NS_OK;
+
+ nsIMutableArray *list = m_IsMailList ? m_AddressList : mCardList;
+
+ if (!list)
+ return NS_OK;
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card;
+ nsAutoCString cardValue;
+
+ for (uint32_t i = 0; i < length && !*aResult; ++i)
+ {
+ card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = card->GetPropertyAsAUTF8String(aProperty, cardValue);
+ if (NS_SUCCEEDED(rv))
+ {
+#ifdef MOZILLA_INTERNAL_API
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue) :
+ cardValue.Equals(aValue, nsCaseInsensitiveCStringComparator());
+#else
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue) :
+ cardValue.Equals(aValue, CaseInsensitiveCompare);
+#endif
+ if (equal)
+ NS_IF_ADDREF(*aResult = card);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool aCaseSensitive,
+ nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (aValue.IsEmpty())
+ return NS_NewEmptyEnumerator(aResult);
+
+ nsIMutableArray *list = m_IsMailList ? m_AddressList : mCardList;
+
+ if (!list)
+ return NS_NewEmptyEnumerator(aResult);
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMArray<nsIAbCard> resultArray;
+ nsCOMPtr<nsIAbCard> card;
+ nsAutoCString cardValue;
+
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = card->GetPropertyAsAUTF8String(aProperty, cardValue);
+ if (NS_SUCCEEDED(rv))
+ {
+#ifdef MOZILLA_INTERNAL_API
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue) :
+ cardValue.Equals(aValue, nsCaseInsensitiveCStringComparator());
+#else
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue) :
+ cardValue.Equals(aValue, CaseInsensitiveCompare);
+#endif
+ if (equal)
+ resultArray.AppendObject(card);
+ }
+ }
+ }
+
+ return NS_NewArrayEnumerator(aResult, resultArray);
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::CardForEmailAddress(const nsACString &aEmailAddress,
+ nsIAbCard **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (aEmailAddress.IsEmpty())
+ return NS_OK;
+
+ nsIMutableArray *list = m_IsMailList ? m_AddressList : mCardList;
+
+ if (!list)
+ return NS_OK;
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card;
+
+ for (uint32_t i = 0; i < length && !*aResult; ++i)
+ {
+ card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool hasEmailAddress = false;
+
+ rv = card->HasEmailAddress(aEmailAddress, &hasEmailAddress);
+ if (NS_SUCCEEDED(rv) && hasEmailAddress)
+ NS_IF_ADDREF(*aResult = card);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::HasCard(nsIAbCard *aCard, bool *aHasCard)
+{
+ NS_ENSURE_ARG_POINTER(aCard);
+ NS_ENSURE_ARG_POINTER(aHasCard);
+
+ nsresult rv = NS_OK;
+ uint32_t index;
+ if (m_IsMailList)
+ {
+ if (m_AddressList)
+ rv = m_AddressList->IndexOf(0, aCard, &index);
+ }
+ else if (mCardList)
+ rv = mCardList->IndexOf(0, aCard, &index);
+
+ *aHasCard = NS_SUCCEEDED(rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::HasDirectory(nsIAbDirectory *aDirectory,
+ bool *aHasDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_ENSURE_ARG_POINTER(aHasDirectory);
+
+ *aHasDirectory = false;
+
+ uint32_t pos;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ *aHasDirectory = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::OnSearchFinished(int32_t aResult, const nsAString &aErrorMsg)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::OnSearchFoundCard(nsIAbCard *aCard)
+{
+ nsresult rv;
+ if (!m_AddressList) {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mCardList) {
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = m_AddressList->AppendElement(aCard, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mCardList->AppendElement(aCard, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+ aCard->SetDirectoryId(ourUuid);
+
+ return NS_OK;
+}
+
+nsresult
+nsAbOSXDirectory::FallbackSearch(nsIAbBooleanExpression *aExpression,
+ nsISimpleEnumerator **aCards)
+{
+ nsresult rv;
+
+ if (mCardList)
+ rv = mCardList->Clear();
+ else
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_AddressList) {
+ m_AddressList->Clear();
+ }
+ else {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments =
+ do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetExpression(aExpression);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't search the subdirectories. If the current directory is a mailing
+ // list, it won't have any subdirectories. If the current directory is an
+ // addressbook, searching both it and the subdirectories (the mailing
+ // lists), will yield duplicate results because every entry in a mailing
+ // list will be an entry in the parent addressbook.
+ rv = arguments->SetQuerySubDirectories(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the directory without the query
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initiate the proxy query with the no query directory
+ nsCOMPtr<nsIAbDirectoryQueryProxy> queryProxy =
+ do_CreateInstance(NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = queryProxy->Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t context = 0;
+ rv = queryProxy->DoQuery(directory, arguments, this, -1, 0, &context);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewArrayEnumerator(aCards, m_AddressList);
+}
+
+nsresult nsAbOSXDirectory::DeleteUid(const nsACString &aUid)
+{
+ if (!m_AddressList)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // At this stage we don't know if aUid represents a card or group. The OS X
+ // interfaces don't give us chance to find out, so we have to go through
+ // our lists to find it.
+
+ // First, we'll see if its in the group list as it is likely to be shorter.
+
+ // See if this item is in our address list
+ uint32_t addressCount;
+ rv = m_AddressList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri(NS_ABOSXDIRECTORY_URI_PREFIX);
+ uri.Append(aUid);
+
+ // Iterate backwards in case we remove something
+ while (addressCount--)
+ {
+ nsCOMPtr<nsIAbItem> abItem(do_QueryElementAt(m_AddressList,
+ addressCount, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIAbDirectory> directory(do_QueryInterface(abItem, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString dirUri;
+ directory->GetURI(dirUri);
+ if (uri.Equals(dirUri))
+ return UnassertDirectory(abManager, directory);
+ } else {
+ nsCOMPtr<nsIAbOSXCard> osxCard(do_QueryInterface(abItem, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString cardUri;
+ osxCard->GetURI(cardUri);
+ if (uri.Equals(cardUri))
+ {
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(osxCard, &rv));
+ if (NS_SUCCEEDED(rv))
+ return UnassertCard(abManager, card, m_AddressList);
+ }
+ }
+ }
+ }
+
+ // Second, see if it is one of the cards.
+ if (!mCardList)
+ return NS_ERROR_FAILURE;
+
+ uri = NS_ABOSXCARD_URI_PREFIX;
+ uri.Append(aUid);
+
+ rv = mCardList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (addressCount--)
+ {
+ nsCOMPtr<nsIAbOSXCard> osxCard(do_QueryElementAt(mCardList, addressCount, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsAutoCString cardUri;
+ osxCard->GetURI(cardUri);
+
+ if (uri.Equals(cardUri)) {
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(osxCard, &rv));
+ if (NS_SUCCEEDED(rv))
+ return UnassertCard(abManager, card, mCardList);
+ }
+ }
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbOSXUtils.h b/mailnews/addrbook/src/nsAbOSXUtils.h
new file mode 100644
index 000000000..fc2f2a546
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXUtils.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbOSXUtils_h___
+#define nsAbOSXUtils_h___
+
+#include <Foundation/NSString.h>
+#include "nsStringGlue.h"
+
+class nsString;
+class nsCString;
+class nsAbCardProperty;
+
+NSString *WrapString(const nsString &aString);
+void AppendToString(const NSString *aString, nsString &aResult);
+void AssignToString(const NSString *aString, nsString &aResult);
+void AppendToCString(const NSString *aString, nsCString &aResult);
+
+struct nsAbOSXPropertyMap
+{
+ NSString * const mOSXProperty;
+ NSString * const mOSXLabel;
+ NSString * const mOSXKey;
+ const char *mPropertyName;
+};
+
+class nsAbOSXUtils
+{
+public:
+ static const nsAbOSXPropertyMap kPropertyMap[];
+ static const uint32_t kPropertyMapSize;
+};
+
+#endif // nsAbOSXUtils_h___
diff --git a/mailnews/addrbook/src/nsAbOSXUtils.mm b/mailnews/addrbook/src/nsAbOSXUtils.mm
new file mode 100644
index 000000000..60802d772
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXUtils.mm
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbOSXUtils.h"
+#include "nsStringGlue.h"
+#include "nsAbOSXCard.h"
+#include "nsMemory.h"
+#include "mozilla/ArrayUtils.h"
+using namespace mozilla;
+
+#include <AddressBook/AddressBook.h>
+#define kABDepartmentProperty (kABDepartmentProperty ? kABDepartmentProperty : @"ABDepartment")
+
+NSString*
+WrapString(const nsString &aString)
+{
+ unichar* chars = reinterpret_cast<unichar*>(const_cast<char16_t*>(aString.get()));
+
+ return [NSString stringWithCharacters:chars
+ length:aString.Length()];
+}
+
+void
+AppendToString(const NSString *aString, nsString &aResult)
+{
+ if (aString) {
+ const char *chars = [aString UTF8String];
+ if (chars) {
+ aResult.Append(NS_ConvertUTF8toUTF16(chars));
+ }
+ }
+}
+
+void
+AssignToString(const NSString *aString, nsString &aResult)
+{
+ if (aString) {
+ const char *chars = [aString UTF8String];
+ if (chars)
+ CopyUTF8toUTF16(nsDependentCString(chars), aResult);
+ }
+}
+
+void
+AppendToCString(const NSString *aString, nsCString &aResult)
+{
+ if (aString) {
+ const char *chars = [aString UTF8String];
+ if (chars) {
+ aResult.Append(chars);
+ }
+ }
+}
+
+// Some properties can't be easily mapped back and forth.
+#define DONT_MAP(moz_name, osx_property, osx_label, osx_key)
+
+#define DEFINE_PROPERTY(moz_name, osx_property, osx_label, osx_key) \
+ { osx_property, osx_label, osx_key, #moz_name },
+
+const nsAbOSXPropertyMap nsAbOSXUtils::kPropertyMap[] = {
+ DEFINE_PROPERTY(FirstName, kABFirstNameProperty, nil, nil)
+ DEFINE_PROPERTY(LastName, kABLastNameProperty, nil, nil)
+ DONT_MAP("DisplayName", nil, nil, nil)
+ DEFINE_PROPERTY(PhoneticFirstName, kABFirstNamePhoneticProperty, nil, nil)
+ DEFINE_PROPERTY(PhoneticLastName, kABLastNamePhoneticProperty, nil, nil)
+ DEFINE_PROPERTY(NickName, kABNicknameProperty, nil, nil)
+ DONT_MAP(PrimaryEmail, kABEmailProperty, nil, nil)
+ DONT_MAP(SecondEmail, kABEmailProperty, nil, nil)
+ DEFINE_PROPERTY(WorkPhone, kABPhoneProperty, kABPhoneWorkLabel, nil)
+ DEFINE_PROPERTY(HomePhone, kABPhoneProperty, kABPhoneHomeLabel, nil)
+ DEFINE_PROPERTY(FaxNumber, kABPhoneProperty, kABPhoneWorkFAXLabel, nil)
+ DEFINE_PROPERTY(PagerNumber, kABPhoneProperty, kABPhonePagerLabel, nil)
+ DEFINE_PROPERTY(CellularNumber, kABPhoneProperty, kABPhoneMobileLabel, nil)
+ DEFINE_PROPERTY(HomeAddress, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressStreetKey)
+ DEFINE_PROPERTY(HomeCity, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressCityKey)
+ DEFINE_PROPERTY(HomeState, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressStateKey)
+ DEFINE_PROPERTY(HomeZipCode, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressZIPKey)
+ DEFINE_PROPERTY(HomeCountry, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressCountryKey)
+ DEFINE_PROPERTY(WorkAddress, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressStreetKey)
+ DEFINE_PROPERTY(WorkCity, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressCityKey)
+ DEFINE_PROPERTY(WorkState, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressStateKey)
+ DEFINE_PROPERTY(WorkZipCode, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressZIPKey)
+ DEFINE_PROPERTY(WorkCountry, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressCountryKey)
+ DEFINE_PROPERTY(JobTitle, kABJobTitleProperty, nil, nil)
+ DEFINE_PROPERTY(Department, kABDepartmentProperty, nil, nil)
+ DEFINE_PROPERTY(Company, kABOrganizationProperty, nil, nil)
+ // This was kABAIMInstantProperty previously, but it was deprecated in OS X 10.7.
+ DONT_MAP(_AimScreenName, kABInstantMessageProperty, nil, nil)
+ DEFINE_PROPERTY(WebPage1, kABHomePageProperty, nil, nil)
+ DONT_MAP(WebPage2, kABHomePageProperty, nil, nil)
+ DONT_MAP(BirthYear, "birthyear", nil, nil)
+ DONT_MAP(BirthMonth, "birthmonth", nil, nil)
+ DONT_MAP(BirthDay, "birthday", nil, nil)
+ DONT_MAP(Custom1, "custom1", nil, nil)
+ DONT_MAP(Custom2, "custom2", nil, nil)
+ DONT_MAP(Custom3, "custom3", nil, nil)
+ DONT_MAP(Custom4, "custom4", nil, nil)
+ DEFINE_PROPERTY(Note, kABNoteProperty, nil, nil)
+ DONT_MAP("PreferMailFormat", nil, nil, nil)
+ DONT_MAP("LastModifiedDate", modifytimestamp, nil, nil)
+};
+
+const uint32_t nsAbOSXUtils::kPropertyMapSize =
+ ArrayLength(nsAbOSXUtils::kPropertyMap);
diff --git a/mailnews/addrbook/src/nsAbOutlookDirFactory.cpp b/mailnews/addrbook/src/nsAbOutlookDirFactory.cpp
new file mode 100644
index 000000000..164e5a475
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOutlookDirFactory.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsAbOutlookDirFactory.h"
+#include "nsAbWinHelper.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbManager.h"
+#include "nsEnumeratorUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsAbBaseCID.h"
+#include "mozilla/Logging.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gAbOutlookDirFactoryLog
+ = PR_NewLogModule("nsAbOutlookDirFactoryLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(nsAbOutlookDirFactoryLog, mozilla::LogLevel::Debug, args)
+
+
+NS_IMPL_ISUPPORTS(nsAbOutlookDirFactory, nsIAbDirFactory)
+
+nsAbOutlookDirFactory::nsAbOutlookDirFactory(void)
+{
+}
+
+nsAbOutlookDirFactory::~nsAbOutlookDirFactory(void)
+{
+}
+
+extern const char *kOutlookDirectoryScheme;
+
+NS_IMETHODIMP
+nsAbOutlookDirFactory::GetDirectories(const nsAString &aDirName,
+ const nsACString &aURI,
+ const nsACString &aPrefName,
+ nsISimpleEnumerator **aDirectories)
+{
+ NS_ENSURE_ARG_POINTER(aDirectories);
+
+ *aDirectories = nullptr;
+ nsresult rv = NS_OK;
+ nsCString stub;
+ nsCString entry;
+ nsAbWinType abType = getAbWinType(kOutlookDirectoryScheme,
+ nsCString(aURI).get(), stub, entry);
+
+ if (abType == nsAbWinType_Unknown) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAbWinHelperGuard mapiAddBook(abType);
+ nsMapiEntryArray folders;
+ ULONG nbFolders = 0;
+ nsCOMPtr<nsIMutableArray> directories(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mapiAddBook->IsOK() || !mapiAddBook->GetFolders(folders)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString entryId;
+ nsAutoCString uri;
+
+ for (ULONG i = 0; i < folders.mNbEntries; ++i) {
+ folders.mEntries[i].ToString(entryId);
+ buildAbWinUri(kOutlookDirectoryScheme, abType, uri);
+ uri.Append(entryId);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ directories->AppendElement(directory, false);
+ }
+ return NS_NewArrayEnumerator(aDirectories, directories);
+}
+
+// No actual deletion, since you cannot create the address books from Mozilla.
+NS_IMETHODIMP nsAbOutlookDirFactory::DeleteDirectory(nsIAbDirectory *aDirectory)
+{
+ return NS_OK;
+}
+
diff --git a/mailnews/addrbook/src/nsAbOutlookDirFactory.h b/mailnews/addrbook/src/nsAbOutlookDirFactory.h
new file mode 100644
index 000000000..c66a9c904
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOutlookDirFactory.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsAbOutlookDirFactory_h___
+#define nsAbOutlookDirFactory_h___
+
+#include "nsIAbDirFactory.h"
+
+class nsAbOutlookDirFactory : public nsIAbDirFactory
+{
+public:
+ nsAbOutlookDirFactory(void);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORY
+
+private:
+ virtual ~nsAbOutlookDirFactory(void);
+};
+
+#endif // nsAbOutlookDirFactory_h___
diff --git a/mailnews/addrbook/src/nsAbOutlookDirectory.cpp b/mailnews/addrbook/src/nsAbOutlookDirectory.cpp
new file mode 100644
index 000000000..0d39d1d17
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOutlookDirectory.cpp
@@ -0,0 +1,1539 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsAbOutlookDirectory.h"
+#include "nsAbWinHelper.h"
+
+#include "nsAbBaseCID.h"
+#include "nsIAbCard.h"
+#include "nsStringGlue.h"
+#include "nsAbDirectoryQuery.h"
+#include "nsIAbBooleanExpression.h"
+#include "nsIAbManager.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsAbQueryStringToExpression.h"
+#include "nsAbUtils.h"
+#include "nsEnumeratorUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Logging.h"
+#include "prthread.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsCRTGlue.h"
+#include "nsArrayUtils.h"
+#include "nsArrayEnumerator.h"
+#include "nsMsgUtils.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gAbOutlookDirectoryLog
+ = PR_NewLogModule("nsAbOutlookDirectoryLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(gAbOutlookDirectoryLog, mozilla::LogLevel::Debug, args)
+
+nsAbOutlookDirectory::nsAbOutlookDirectory(void)
+ : nsAbDirProperty(),
+ mCurrentQueryId(0), mSearchContext(-1),
+ mAbWinType(nsAbWinType_Unknown), mMapiData(nullptr)
+{
+ mMapiData = new nsMapiEntry ;
+ mProtector = PR_NewLock() ;
+}
+
+nsAbOutlookDirectory::~nsAbOutlookDirectory(void)
+{
+ if (mMapiData) { delete mMapiData ; }
+ if (mProtector) { PR_DestroyLock(mProtector) ; }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbOutlookDirectory, nsAbDirProperty,
+ nsIAbDirectoryQuery, nsIAbDirectorySearch,
+ nsIAbDirSearchListener)
+
+NS_IMETHODIMP nsAbOutlookDirectory::Init(const char *aUri)
+{
+ nsresult rv = nsAbDirProperty::Init(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString entry;
+ nsAutoCString stub;
+
+ mAbWinType = getAbWinType(kOutlookDirectoryScheme, mURINoQuery.get(), stub, entry);
+ if (mAbWinType == nsAbWinType_Unknown) {
+ PRINTF(("Huge problem URI=%s.\n", mURINoQuery));
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsAbWinHelperGuard mapiAddBook (mAbWinType);
+ nsString prefix;
+ nsAutoString unichars;
+ ULONG objectType = 0;
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ mMapiData->Assign(entry);
+ if (!mapiAddBook->GetPropertyLong(*mMapiData, PR_OBJECT_TYPE, objectType)) {
+ PRINTF(("Cannot get type.\n"));
+ return NS_ERROR_FAILURE;
+ }
+ if (!mapiAddBook->GetPropertyUString(*mMapiData, PR_DISPLAY_NAME_W, unichars)) {
+ PRINTF(("Cannot get name.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mAbWinType == nsAbWinType_Outlook)
+ prefix.AssignLiteral("OP ");
+ else
+ prefix.AssignLiteral("OE ");
+ prefix.Append(unichars);
+
+ if (objectType == MAPI_DISTLIST) {
+ m_IsMailList = true;
+ SetDirName(unichars);
+ }
+ else {
+ m_IsMailList = false;
+ SetDirName(prefix);
+ }
+
+ return UpdateAddressList();
+}
+
+// nsIAbDirectory methods
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetDirType(int32_t *aDirType)
+{
+ NS_ENSURE_ARG_POINTER(aDirType);
+ *aDirType = MAPIDirectory;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetChildNodes(nsISimpleEnumerator **aNodes)
+{
+ NS_ENSURE_ARG_POINTER(aNodes);
+
+ *aNodes = nullptr;
+
+ if (mIsQueryURI) {
+ return NS_NewEmptyEnumerator(aNodes);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> nodeList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetChildNodes(nodeList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewArrayEnumerator(aNodes, nodeList);
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetChildCards(nsISimpleEnumerator **aCards)
+{
+ NS_ENSURE_ARG_POINTER(aCards);
+ *aCards = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> cardList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCardList.Clear();
+
+ rv = mIsQueryURI ? StartSearch() : GetChildCards(cardList, nullptr);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!m_AddressList)
+ {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Fill the results array and update the card list
+ // Also update the address list and notify any changes.
+ uint32_t nbCards = 0;
+
+ NS_NewArrayEnumerator(aCards, cardList);
+ cardList->GetLength(&nbCards);
+
+ nsCOMPtr<nsIAbCard> card;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ for (uint32_t i = 0; i < nbCards; ++i)
+ {
+ card = do_QueryElementAt(cardList, i, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ if (!mCardList.Get(card, nullptr))
+ {
+ // We are dealing with a new element (probably directly
+ // added from Outlook), we may need to sync m_AddressList
+ mCardList.Put(card, card);
+
+ bool isMailList = false;
+
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isMailList)
+ {
+ // We can have mailing lists only in folder,
+ // we must add the directory to m_AddressList
+ nsCString mailListUri;
+ rv = card->GetMailListURI(getter_Copies(mailListUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIAbDirectory> mailList;
+ rv = abManager->GetDirectory(mailListUri, getter_AddRefs(mailList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_AddressList->AppendElement(mailList, false);
+ NotifyItemAddition(mailList);
+ }
+ else if (m_IsMailList)
+ {
+ m_AddressList->AppendElement(card, false);
+ NotifyItemAddition(card);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mIsQueryURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::HasCard(nsIAbCard *aCard, bool *aHasCard)
+{
+ if (!aCard || !aHasCard)
+ return NS_ERROR_NULL_POINTER;
+
+ *aHasCard = mCardList.Get(aCard, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::HasDirectory(nsIAbDirectory *aDirectory, bool *aHasDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_ENSURE_ARG_POINTER(aHasDirectory);
+
+ *aHasDirectory = false;
+
+ uint32_t pos;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ *aHasDirectory = true;
+
+ return NS_OK;
+}
+
+
+static nsresult ExtractCardEntry(nsIAbCard *aCard, nsCString& aEntry)
+{
+ aEntry.Truncate();
+
+ nsCString uri;
+ aCard->GetPropertyAsAUTF8String("OutlookEntryURI", uri);
+
+ // If we don't have a URI, uri will be empty. getAbWinType doesn't set
+ // aEntry to anything if uri is empty, so it will be truncated, allowing us
+ // to accept cards not initialized by us.
+ nsAutoCString stub;
+ getAbWinType(kOutlookCardScheme, uri.get(), stub, aEntry);
+ return NS_OK;
+}
+
+static nsresult ExtractDirectoryEntry(nsIAbDirectory *aDirectory, nsCString& aEntry)
+{
+ aEntry.Truncate();
+ nsCString uri;
+ nsresult rv = aDirectory->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString stub;
+ nsAbWinType objType = getAbWinType(kOutlookDirectoryScheme, uri.get(), stub, aEntry);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DeleteCards(nsIArray *aCardList)
+{
+ if (mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; }
+ if (!aCardList) { return NS_ERROR_NULL_POINTER ; }
+ uint32_t nbCards = 0 ;
+ nsresult retCode = NS_OK ;
+ nsAbWinHelperGuard mapiAddBook (mAbWinType) ;
+
+ if (!mapiAddBook->IsOK()) { return NS_ERROR_FAILURE ; }
+
+ retCode = aCardList->GetLength(&nbCards);
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ uint32_t i = 0 ;
+ nsAutoCString entryString ;
+ nsMapiEntry cardEntry ;
+
+ for (i = 0 ; i < nbCards ; ++ i) {
+ nsCOMPtr<nsIAbCard> card(do_QueryElementAt(aCardList, i, &retCode));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ retCode = ExtractCardEntry(card, entryString) ;
+ if (NS_SUCCEEDED(retCode) && !entryString.IsEmpty()) {
+ card->SetDirectoryId(EmptyCString());
+
+ cardEntry.Assign(entryString) ;
+ if (!mapiAddBook->DeleteEntry(*mMapiData, cardEntry)) {
+ PRINTF(("Cannot delete card %s.\n", entryString.get())) ;
+ }
+ else {
+ mCardList.Remove(card);
+ if (m_IsMailList && m_AddressList)
+ {
+ uint32_t pos;
+ if (NS_SUCCEEDED(m_AddressList->IndexOf(0, card, &pos)))
+ m_AddressList->RemoveElementAt(pos);
+ }
+ retCode = NotifyItemDeletion(card);
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ }
+ }
+ else {
+ PRINTF(("Card doesn't belong in this directory.\n")) ;
+ }
+ }
+ return NS_OK ;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DeleteDirectory(nsIAbDirectory *aDirectory)
+{
+ if (mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; }
+ if (!aDirectory) { return NS_ERROR_NULL_POINTER ; }
+ nsresult retCode = NS_OK ;
+ nsAbWinHelperGuard mapiAddBook (mAbWinType) ;
+ nsAutoCString entryString ;
+
+ if (!mapiAddBook->IsOK()) { return NS_ERROR_FAILURE ; }
+ retCode = ExtractDirectoryEntry(aDirectory, entryString) ;
+ if (NS_SUCCEEDED(retCode) && !entryString.IsEmpty()) {
+ nsMapiEntry directoryEntry ;
+
+ directoryEntry.Assign(entryString) ;
+ if (!mapiAddBook->DeleteEntry(*mMapiData, directoryEntry)) {
+ PRINTF(("Cannot delete directory %s.\n", entryString.get())) ;
+ }
+ else {
+ uint32_t pos;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ m_AddressList->RemoveElementAt(pos);
+
+ retCode = NotifyItemDeletion(aDirectory);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ }
+ }
+ else {
+ PRINTF(("Directory doesn't belong to this folder.\n")) ;
+ }
+ return retCode ;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::AddCard(nsIAbCard *aData, nsIAbCard **addedCard)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ NS_ENSURE_ARG_POINTER(aData);
+
+ nsresult retCode = NS_OK ;
+ bool hasCard = false ;
+
+ retCode = HasCard(aData, &hasCard) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ if (hasCard) {
+ PRINTF(("Has card.\n")) ;
+ NS_IF_ADDREF(*addedCard = aData);
+ return NS_OK ;
+ }
+ retCode = CreateCard(aData, addedCard) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+
+ mCardList.Put(*addedCard, *addedCard);
+
+ if (!m_AddressList)
+ {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &retCode);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ }
+
+ if (m_IsMailList)
+ m_AddressList->AppendElement(*addedCard, false);
+ NotifyItemAddition(*addedCard) ;
+ return retCode ;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DropCard(nsIAbCard *aData, bool needToCopyCard)
+{
+ nsCOMPtr <nsIAbCard> addedCard;
+ return AddCard(aData, getter_AddRefs(addedCard));
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::AddMailList(nsIAbDirectory *aMailList, nsIAbDirectory **addedList)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+ NS_ENSURE_ARG_POINTER(aMailList);
+ NS_ENSURE_ARG_POINTER(addedList);
+ if (m_IsMailList)
+ return NS_OK;
+ nsAbWinHelperGuard mapiAddBook (mAbWinType);
+ nsAutoCString entryString;
+ nsMapiEntry newEntry;
+ bool didCopy = false;
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+ nsresult rv = ExtractDirectoryEntry(aMailList, entryString);
+ if (NS_SUCCEEDED(rv) && !entryString.IsEmpty())
+ {
+ nsMapiEntry sourceEntry;
+
+ sourceEntry.Assign(entryString);
+ mapiAddBook->CopyEntry(*mMapiData, sourceEntry, newEntry);
+ }
+ if (newEntry.mByteCount == 0)
+ {
+ if (!mapiAddBook->CreateDistList(*mMapiData, newEntry))
+ return NS_ERROR_FAILURE;
+ }
+ else {
+ didCopy = true;
+ }
+ newEntry.ToString(entryString);
+ nsAutoCString uri;
+
+ buildAbWinUri(kOutlookDirectoryScheme, mAbWinType, uri);
+ uri.Append(entryString);
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> newList;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(newList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!didCopy)
+ {
+ rv = newList->CopyMailList(aMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = newList->EditMailListToDatabase(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!m_AddressList)
+ {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ m_AddressList->AppendElement(newList, false);
+ NotifyItemAddition(newList);
+ NS_IF_ADDREF(*addedList = newList);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::EditMailListToDatabase(nsIAbCard *listCard)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsresult rv;
+ nsString name;
+ nsAbWinHelperGuard mapiAddBook(mAbWinType);
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ rv = GetDirName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mapiAddBook->SetPropertyUString(*mMapiData, PR_DISPLAY_NAME_W,
+ name.get()))
+ return NS_ERROR_FAILURE;
+
+ return CommitAddressList();
+}
+
+struct OutlookTableAttr
+{
+ const char *mOuterName ;
+ ULONG mMapiProp ;
+} ;
+
+// Here, we are forced to use the Ascii versions of the properties
+// instead of the widechar ones, because the content restriction
+// operators do not work on unicode strings in mapi.
+static const OutlookTableAttr OutlookTableStringToProp [] =
+{
+ {kFirstNameProperty, PR_GIVEN_NAME_A},
+ {kLastNameProperty, PR_SURNAME_A},
+ {kDisplayNameProperty, PR_DISPLAY_NAME_A},
+ {kNicknameProperty, PR_NICKNAME_A},
+ {kPriEmailProperty, PR_EMAIL_ADDRESS_A},
+ {kWorkPhoneProperty, PR_BUSINESS_TELEPHONE_NUMBER_A},
+ {kHomePhoneProperty, PR_HOME_TELEPHONE_NUMBER_A},
+ {kFaxProperty, PR_BUSINESS_FAX_NUMBER_A},
+ {kPagerProperty, PR_PAGER_TELEPHONE_NUMBER_A},
+ {kCellularProperty, PR_MOBILE_TELEPHONE_NUMBER_A},
+ {kHomeAddressProperty, PR_HOME_ADDRESS_STREET_A},
+ {kHomeCityProperty, PR_HOME_ADDRESS_CITY_A},
+ {kHomeStateProperty, PR_HOME_ADDRESS_STATE_OR_PROVINCE_A},
+ {kHomeZipCodeProperty, PR_HOME_ADDRESS_POSTAL_CODE_A},
+ {kHomeCountryProperty, PR_HOME_ADDRESS_COUNTRY_A},
+ {kWorkAddressProperty, PR_BUSINESS_ADDRESS_STREET_A},
+ {kWorkCityProperty, PR_BUSINESS_ADDRESS_CITY_A},
+ {kWorkStateProperty, PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A},
+ {kWorkZipCodeProperty, PR_BUSINESS_ADDRESS_POSTAL_CODE_A},
+ {kWorkCountryProperty, PR_BUSINESS_ADDRESS_COUNTRY_A},
+ {kJobTitleProperty, PR_TITLE_A},
+ {kDepartmentProperty, PR_DEPARTMENT_NAME_A},
+ {kCompanyProperty, PR_COMPANY_NAME_A},
+ {kWorkWebPageProperty, PR_BUSINESS_HOME_PAGE_A},
+ {kHomeWebPageProperty, PR_PERSONAL_HOME_PAGE_A},
+ // For the moment, we don't support querying on the birthday
+ // sub-elements.
+#if 0
+ {kBirthYearProperty, PR_BIRTHDAY},
+ {kBirthMonthProperty, PR_BIRTHDAY},
+ {kBirthDayProperty, PR_BIRTHDAY},
+#endif // 0
+ {kNotesProperty, PR_COMMENT_A}
+} ;
+
+static const uint32_t OutlookTableNbProps = sizeof(OutlookTableStringToProp) /
+ sizeof(OutlookTableStringToProp [0]) ;
+
+static ULONG findPropertyTag(const char *aName) {
+ uint32_t i = 0 ;
+
+ for (i = 0 ; i < OutlookTableNbProps ; ++ i) {
+ if (strcmp(aName, OutlookTableStringToProp [i].mOuterName) == 0) {
+ return OutlookTableStringToProp [i].mMapiProp ;
+ }
+ }
+ return 0 ;
+}
+
+static nsresult BuildRestriction(nsIAbBooleanConditionString *aCondition,
+ SRestriction& aRestriction,
+ bool& aSkipItem)
+{
+ if (!aCondition) { return NS_ERROR_NULL_POINTER ; }
+ aSkipItem = false ;
+ nsAbBooleanConditionType conditionType = 0 ;
+ nsresult retCode = NS_OK ;
+ nsCString name;
+ nsString value;
+ ULONG propertyTag = 0 ;
+ nsAutoCString valueAscii ;
+
+ retCode = aCondition->GetCondition(&conditionType) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = aCondition->GetName(getter_Copies(name)) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = aCondition->GetValue(getter_Copies(value)) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ LossyCopyUTF16toASCII(value, valueAscii);
+ propertyTag = findPropertyTag(name.get()) ;
+ if (propertyTag == 0) {
+ aSkipItem = true ;
+ return retCode ;
+ }
+ switch (conditionType) {
+ case nsIAbBooleanConditionTypes::Exists :
+ aRestriction.rt = RES_EXIST ;
+ aRestriction.res.resExist.ulPropTag = propertyTag ;
+ break ;
+ case nsIAbBooleanConditionTypes::DoesNotExist :
+ aRestriction.rt = RES_NOT ;
+ aRestriction.res.resNot.lpRes = new SRestriction ;
+ aRestriction.res.resNot.lpRes->rt = RES_EXIST ;
+ aRestriction.res.resNot.lpRes->res.resExist.ulPropTag = propertyTag ;
+ break ;
+ case nsIAbBooleanConditionTypes::Contains :
+ aRestriction.rt = RES_CONTENT ;
+ aRestriction.res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_LOOSE ;
+ aRestriction.res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::DoesNotContain :
+ aRestriction.rt = RES_NOT ;
+ aRestriction.res.resNot.lpRes = new SRestriction ;
+ aRestriction.res.resNot.lpRes->rt = RES_CONTENT ;
+ aRestriction.res.resNot.lpRes->res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_LOOSE ;
+ aRestriction.res.resNot.lpRes->res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::Is :
+ aRestriction.rt = RES_CONTENT ;
+ aRestriction.res.resContent.ulFuzzyLevel = FL_FULLSTRING | FL_LOOSE ;
+ aRestriction.res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::IsNot :
+ aRestriction.rt = RES_NOT ;
+ aRestriction.res.resNot.lpRes = new SRestriction ;
+ aRestriction.res.resNot.lpRes->rt = RES_CONTENT ;
+ aRestriction.res.resNot.lpRes->res.resContent.ulFuzzyLevel = FL_FULLSTRING | FL_LOOSE ;
+ aRestriction.res.resNot.lpRes->res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::BeginsWith :
+ aRestriction.rt = RES_CONTENT ;
+ aRestriction.res.resContent.ulFuzzyLevel = FL_PREFIX | FL_LOOSE ;
+ aRestriction.res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::EndsWith :
+ // This condition should be implemented through regular expressions,
+ // but MAPI doesn't match them correctly.
+#if 0
+ aRestriction.rt = RES_PROPERTY ;
+ aRestriction.res.resProperty.relop = RELOP_RE ;
+ aRestriction.res.resProperty.ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp = new SPropValue ;
+ aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+#else
+ aSkipItem = true ;
+#endif // 0
+ break ;
+ case nsIAbBooleanConditionTypes::SoundsLike :
+ // This condition cannot be implemented in MAPI.
+ aSkipItem = true ;
+ break ;
+ case nsIAbBooleanConditionTypes::RegExp :
+ // This condition should be implemented this way, but the following
+ // code will never match (through MAPI's fault).
+#if 0
+ aRestriction.rt = RES_PROPERTY ;
+ aRestriction.res.resProperty.relop = RELOP_RE ;
+ aRestriction.res.resProperty.ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp = new SPropValue ;
+ aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+#else
+ aSkipItem = true ;
+#endif // 0
+ break ;
+ case nsIAbBooleanConditionTypes::LessThan :
+ aRestriction.rt = RES_PROPERTY ;
+ aRestriction.res.resProperty.relop = RELOP_LT ;
+ aRestriction.res.resProperty.ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp = new SPropValue ;
+ aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::GreaterThan :
+ aRestriction.rt = RES_PROPERTY ;
+ aRestriction.res.resProperty.relop = RELOP_GT ;
+ aRestriction.res.resProperty.ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp = new SPropValue ;
+ aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ default :
+ aSkipItem = true ;
+ break ;
+ }
+ return retCode ;
+}
+
+static nsresult BuildRestriction(nsIAbBooleanExpression *aLevel,
+ SRestriction& aRestriction)
+{
+ if (!aLevel) { return NS_ERROR_NULL_POINTER ; }
+ aRestriction.rt = RES_COMMENT ;
+ nsresult retCode = NS_OK ;
+ nsAbBooleanOperationType operationType = 0 ;
+ uint32_t nbExpressions = 0 ;
+ nsCOMPtr<nsIArray> expressions;
+
+ retCode = aLevel->GetOperation(&operationType);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ retCode = aLevel->GetExpressions(getter_AddRefs(expressions));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ retCode = expressions->GetLength(&nbExpressions);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ if (nbExpressions == 0) {
+ PRINTF(("Error, no expressions.\n")) ;
+ return NS_OK ;
+ }
+ if (operationType == nsIAbBooleanOperationTypes::NOT && nbExpressions != 1) {
+ PRINTF(("Error, unary operation NOT with multiple operands.\n")) ;
+ return NS_OK ;
+ }
+ LPSRestriction restrictionArray = new SRestriction [nbExpressions] ;
+ uint32_t realNbExpressions = 0 ;
+ bool skipItem = false ;
+ uint32_t i = 0 ;
+
+ nsCOMPtr<nsIAbBooleanConditionString> condition;
+ nsCOMPtr<nsIAbBooleanExpression> subExpression;
+
+ for (i = 0; i < nbExpressions; ++i) {
+ condition = do_QueryElementAt(expressions, i, &retCode);
+
+ if (NS_SUCCEEDED(retCode)) {
+ retCode = BuildRestriction(condition, *restrictionArray, skipItem);
+ if (NS_SUCCEEDED(retCode)) {
+ if (!skipItem) {
+ ++restrictionArray;
+ ++realNbExpressions;
+ }
+ }
+ else
+ PRINTF(("Cannot build restriction for item %d %08x.\n", i, retCode));
+ }
+ else {
+ subExpression = do_QueryElementAt(expressions, i, &retCode);
+
+ if (NS_SUCCEEDED(retCode)) {
+ retCode = BuildRestriction(subExpression, *restrictionArray);
+ if (NS_SUCCEEDED(retCode)) {
+ if (restrictionArray->rt != RES_COMMENT) {
+ ++restrictionArray;
+ ++realNbExpressions;
+ }
+ }
+ }
+ else
+ PRINTF(("Cannot get interface for item %d %08x.\n", i, retCode));
+ }
+ }
+
+ restrictionArray -= realNbExpressions ;
+ if (realNbExpressions > 1) {
+ if (operationType == nsIAbBooleanOperationTypes::OR) {
+ aRestriction.rt = RES_OR ;
+ aRestriction.res.resOr.lpRes = restrictionArray ;
+ aRestriction.res.resOr.cRes = realNbExpressions ;
+ }
+ else if (operationType == nsIAbBooleanOperationTypes::AND) {
+ aRestriction.rt = RES_AND ;
+ aRestriction.res.resAnd.lpRes = restrictionArray ;
+ aRestriction.res.resAnd.cRes = realNbExpressions ;
+ }
+ else {
+ PRINTF(("Unsupported operation %d.\n", operationType)) ;
+ }
+ }
+ else if (realNbExpressions == 1) {
+ if (operationType == nsIAbBooleanOperationTypes::NOT) {
+ aRestriction.rt = RES_NOT ;
+ // This copy is to ensure that every NOT restriction is being
+ // allocated by new and not new[] (see destruction of restriction)
+ aRestriction.res.resNot.lpRes = new SRestriction ;
+ memcpy(aRestriction.res.resNot.lpRes, restrictionArray, sizeof(SRestriction)) ;
+ }
+ else {
+ // Case where the restriction array is redundant,
+ // we need to fill the restriction directly.
+ memcpy(&aRestriction, restrictionArray, sizeof(SRestriction)) ;
+ }
+ delete [] restrictionArray ;
+ }
+ if (aRestriction.rt == RES_COMMENT) {
+ // This means we haven't really built any useful expression
+ delete [] restrictionArray ;
+ }
+ return NS_OK ;
+}
+
+static nsresult BuildRestriction(nsIAbDirectoryQueryArguments *aArguments,
+ SRestriction& aRestriction)
+{
+ if (!aArguments) { return NS_ERROR_NULL_POINTER ; }
+ nsresult retCode = NS_OK ;
+
+ nsCOMPtr<nsISupports> supports ;
+ retCode = aArguments->GetExpression(getter_AddRefs(supports)) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ nsCOMPtr<nsIAbBooleanExpression> booleanQuery =
+ do_QueryInterface(supports, &retCode) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = BuildRestriction(booleanQuery, aRestriction) ;
+ return retCode ;
+}
+
+static void DestroyRestriction(SRestriction& aRestriction)
+{
+ switch(aRestriction.rt) {
+ case RES_AND :
+ case RES_OR :
+ {
+ for (ULONG i = 0 ; i < aRestriction.res.resAnd.cRes ; ++ i) {
+ DestroyRestriction(aRestriction.res.resAnd.lpRes [i]) ;
+ }
+ delete [] aRestriction.res.resAnd.lpRes ;
+ }
+ break ;
+ case RES_COMMENT :
+ break ;
+ case RES_CONTENT :
+ if (PROP_TYPE(aRestriction.res.resContent.ulPropTag) == PT_UNICODE) {
+ NS_Free(aRestriction.res.resContent.lpProp->Value.lpszW) ;
+ }
+ else if (PROP_TYPE(aRestriction.res.resContent.ulPropTag) == PT_STRING8) {
+ NS_Free(aRestriction.res.resContent.lpProp->Value.lpszA) ;
+ }
+ delete aRestriction.res.resContent.lpProp ;
+ break ;
+ case RES_EXIST :
+ break ;
+ case RES_NOT :
+ DestroyRestriction(*aRestriction.res.resNot.lpRes) ;
+ delete aRestriction.res.resNot.lpRes ;
+ break ;
+ case RES_BITMASK :
+ case RES_COMPAREPROPS :
+ break ;
+ case RES_PROPERTY :
+ if (PROP_TYPE(aRestriction.res.resProperty.ulPropTag) == PT_UNICODE) {
+ NS_Free(aRestriction.res.resProperty.lpProp->Value.lpszW) ;
+ }
+ else if (PROP_TYPE(aRestriction.res.resProperty.ulPropTag) == PT_STRING8) {
+ NS_Free(aRestriction.res.resProperty.lpProp->Value.lpszA) ;
+ }
+ delete aRestriction.res.resProperty.lpProp ;
+ case RES_SIZE :
+ case RES_SUBRESTRICTION :
+ break ;
+ }
+}
+
+struct QueryThreadArgs
+{
+ nsAbOutlookDirectory *mThis ;
+ SRestriction mRestriction ;
+ nsCOMPtr<nsIAbDirSearchListener> mListener ;
+ int32_t mResultLimit ;
+ int32_t mTimeout ;
+ int32_t mThreadId ;
+} ;
+
+static void QueryThreadFunc(void *aArguments)
+{
+ QueryThreadArgs *arguments = reinterpret_cast<QueryThreadArgs *>(aArguments) ;
+
+ if (!aArguments) { return ; }
+ arguments->mThis->ExecuteQuery(arguments->mRestriction, arguments->mListener,
+ arguments->mResultLimit, arguments->mTimeout,
+ arguments->mThreadId) ;
+ DestroyRestriction(arguments->mRestriction) ;
+ delete arguments ;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DoQuery(nsIAbDirectory *aDirectory,
+ nsIAbDirectoryQueryArguments *aArguments,
+ nsIAbDirSearchListener *aListener,
+ int32_t aResultLimit, int32_t aTimeout,
+ int32_t *aReturnValue)
+{
+ if (!aArguments || !aListener || !aReturnValue) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aReturnValue = -1;
+
+ QueryThreadArgs *threadArgs = new QueryThreadArgs;
+ PRThread *newThread = nullptr;
+
+ if (!threadArgs)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = BuildRestriction(aArguments, threadArgs->mRestriction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ threadArgs->mThis = this;
+ threadArgs->mListener = aListener;
+ threadArgs->mResultLimit = aResultLimit;
+ threadArgs->mTimeout = aTimeout;
+
+ PR_Lock(mProtector);
+ *aReturnValue = ++mCurrentQueryId;
+ PR_Unlock(mProtector);
+
+ threadArgs->mThreadId = *aReturnValue;
+ newThread = PR_CreateThread(PR_USER_THREAD,
+ QueryThreadFunc,
+ threadArgs,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD,
+ 0);
+
+ if (!newThread ) {
+ DestroyRestriction(threadArgs->mRestriction);
+ delete threadArgs;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mQueryThreads.Put(*aReturnValue, newThread);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::StopQuery(int32_t aContext)
+{
+ PRThread *queryThread;
+ if (mQueryThreads.Get(aContext, &queryThread)) {
+ PR_Interrupt(queryThread);
+ mQueryThreads.Remove(aContext);
+ }
+ return NS_OK;
+}
+
+// nsIAbDirectorySearch methods
+NS_IMETHODIMP nsAbOutlookDirectory::StartSearch(void)
+{
+ if (!mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; }
+ nsresult retCode = NS_OK ;
+
+ retCode = StopSearch() ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ mCardList.Clear();
+
+ nsCOMPtr<nsIAbBooleanExpression> expression ;
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID,&retCode);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ retCode = nsAbQueryStringToExpression::Convert(mQueryString, getter_AddRefs(expression)) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = arguments->SetExpression(expression) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+
+ retCode = arguments->SetQuerySubDirectories(true) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+
+ return DoQuery(this, arguments, this, -1, 0, &mSearchContext);
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::StopSearch(void)
+{
+ if (!mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; }
+ return StopQuery(mSearchContext) ;
+}
+
+// nsIAbDirSearchListener
+NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFinished(int32_t aResult,
+ const nsAString &aErrorMsg)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFoundCard(nsIAbCard *aCard)
+{
+ mCardList.Put(aCard, aCard);
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->NotifyDirectoryItemAdded(this, aCard);
+
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::ExecuteQuery(SRestriction &aRestriction,
+ nsIAbDirSearchListener *aListener,
+ int32_t aResultLimit, int32_t aTimeout,
+ int32_t aThreadId)
+
+{
+ if (!aListener)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult retCode = NS_OK;
+
+ nsCOMPtr<nsIMutableArray> resultsArray(do_CreateInstance(NS_ARRAY_CONTRACTID,
+ &retCode));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ retCode = GetChildCards(resultsArray,
+ aRestriction.rt == RES_COMMENT ? nullptr : &aRestriction);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ uint32_t nbResults = 0;
+ retCode = resultsArray->GetLength(&nbResults);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ if (aResultLimit > 0 && nbResults > static_cast<uint32_t>(aResultLimit)) {
+ nbResults = static_cast<uint32_t>(aResultLimit) ;
+ }
+
+ uint32_t i = 0;
+ nsCOMPtr<nsIAbCard> card;
+
+ for (i = 0 ; i < nbResults ; ++ i) {
+ card = do_QueryElementAt(resultsArray, i, &retCode);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ aListener->OnSearchFoundCard(card);
+ }
+
+ mQueryThreads.Remove(aThreadId);
+
+ aListener->OnSearchFinished(nsIAbDirectoryQueryResultListener::queryResultComplete,
+ EmptyString());
+ return retCode;
+}
+
+// This function expects the aCards array to already be created.
+nsresult nsAbOutlookDirectory::GetChildCards(nsIMutableArray *aCards,
+ void *aRestriction)
+{
+ nsAbWinHelperGuard mapiAddBook(mAbWinType);
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ nsMapiEntryArray cardEntries;
+ LPSRestriction restriction = (LPSRestriction) aRestriction;
+
+ if (!mapiAddBook->GetCards(*mMapiData, restriction, cardEntries)) {
+ PRINTF(("Cannot get cards.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+
+ nsAutoCString entryId;
+ nsAutoCString uriName;
+ nsCOMPtr<nsIAbCard> childCard;
+ nsresult rv;
+
+ for (ULONG card = 0; card < cardEntries.mNbEntries; ++card) {
+ cardEntries.mEntries[card].ToString(entryId);
+ buildAbWinUri(kOutlookCardScheme, mAbWinType, uriName);
+ uriName.Append(entryId);
+
+ rv = OutlookCardForURI(uriName, getter_AddRefs(childCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ childCard->SetDirectoryId(ourUuid);
+
+ aCards->AppendElement(childCard, false);
+ }
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::GetChildNodes(nsIMutableArray* aNodes)
+{
+ NS_ENSURE_ARG_POINTER(aNodes);
+
+ aNodes->Clear();
+
+ nsAbWinHelperGuard mapiAddBook(mAbWinType);
+ nsMapiEntryArray nodeEntries;
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ if (!mapiAddBook->GetNodes(*mMapiData, nodeEntries))
+ {
+ PRINTF(("Cannot get nodes.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString entryId;
+ nsAutoCString uriName;
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (ULONG node = 0; node < nodeEntries.mNbEntries; ++node)
+ {
+ nodeEntries.mEntries[node].ToString(entryId);
+ buildAbWinUri(kOutlookDirectoryScheme, mAbWinType, uriName);
+ uriName.Append(entryId);
+
+ nsCOMPtr <nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(uriName, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aNodes->AppendElement(directory, false);
+ }
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::NotifyItemDeletion(nsISupports *aItem)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->NotifyDirectoryItemDeleted(this, aItem);
+
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::NotifyItemAddition(nsISupports *aItem)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->NotifyDirectoryItemAdded(this, aItem);
+
+ return rv;
+}
+
+// This is called from EditMailListToDatabase.
+// We got m_AddressList containing the list of cards the mailing
+// list is supposed to contain at the end.
+nsresult nsAbOutlookDirectory::CommitAddressList(void)
+{
+ if (!m_IsMailList) {
+ PRINTF(("We are not in a mailing list, no commit can be done.\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ uint32_t i = 0;
+ nsCOMPtr<nsIMutableArray> oldList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetChildCards(oldList, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_AddressList)
+ return NS_ERROR_NULL_POINTER;
+
+ uint32_t nbCards = 0;
+ rv = m_AddressList->GetLength(&nbCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> element;
+ nsCOMPtr<nsIAbCard> newCard;
+ uint32_t pos;
+
+ for (i = 0; i < nbCards; ++i) {
+ element = do_QueryElementAt(m_AddressList, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_SUCCEEDED(oldList->IndexOf(0, element, &pos))) {
+ rv = oldList->RemoveElementAt(pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The entry was not already there
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(element, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CreateCard(card, getter_AddRefs(newCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_AddressList->ReplaceElementAt(newCard, i, false);
+ }
+ }
+ return DeleteCards(oldList);
+}
+
+nsresult nsAbOutlookDirectory::UpdateAddressList(void)
+{
+ if (!m_AddressList)
+ {
+ nsresult rv;
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return m_IsMailList ? GetChildCards(m_AddressList, nullptr) :
+ GetChildNodes(m_AddressList);
+}
+
+nsresult nsAbOutlookDirectory::CreateCard(nsIAbCard *aData, nsIAbCard **aNewCard)
+{
+ if (!aData || !aNewCard) { return NS_ERROR_NULL_POINTER ; }
+ *aNewCard = nullptr ;
+ nsresult retCode = NS_OK ;
+ nsAbWinHelperGuard mapiAddBook (mAbWinType) ;
+ nsMapiEntry newEntry ;
+ nsAutoCString entryString ;
+ bool didCopy = false ;
+
+ if (!mapiAddBook->IsOK()) { return NS_ERROR_FAILURE ; }
+ // If we get an nsIAbCard that maps onto an Outlook card uri
+ // we simply copy the contents of the Outlook card.
+ retCode = ExtractCardEntry(aData, entryString) ;
+ if (NS_SUCCEEDED(retCode) && !entryString.IsEmpty()) {
+ nsMapiEntry sourceEntry ;
+
+
+ sourceEntry.Assign(entryString) ;
+ if (m_IsMailList) {
+ // In the case of a mailing list, we can use the address
+ // as a direct template to build the new one (which is done
+ // by CopyEntry).
+ mapiAddBook->CopyEntry(*mMapiData, sourceEntry, newEntry) ;
+ didCopy = true ;
+ }
+ else {
+ // Else, we have to create a temporary address and copy the
+ // source into it. Yes it's silly.
+ mapiAddBook->CreateEntry(*mMapiData, newEntry) ;
+ }
+ }
+ // If this approach doesn't work, well we're back to creating and copying.
+ if (newEntry.mByteCount == 0) {
+ // In the case of a mailing list, we cannot directly create a new card,
+ // we have to create a temporary one in a real folder (to be able to use
+ // templates) and then copy it to the mailing list.
+ if (m_IsMailList) {
+ nsMapiEntry parentEntry ;
+ nsMapiEntry temporaryEntry ;
+
+ if (!mapiAddBook->GetDefaultContainer(parentEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ if (!mapiAddBook->CreateEntry(parentEntry, temporaryEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ if (!mapiAddBook->CopyEntry(*mMapiData, temporaryEntry, newEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ if (!mapiAddBook->DeleteEntry(parentEntry, temporaryEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ }
+ // If we're on a real address book folder, we can directly create an
+ // empty card.
+ else if (!mapiAddBook->CreateEntry(*mMapiData, newEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ }
+ newEntry.ToString(entryString) ;
+ nsAutoCString uri ;
+
+ buildAbWinUri(kOutlookCardScheme, mAbWinType, uri) ;
+ uri.Append(entryString) ;
+
+ nsCOMPtr<nsIAbCard> newCard;
+ retCode = OutlookCardForURI(uri, getter_AddRefs(newCard));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+ newCard->SetDirectoryId(ourUuid);
+
+ if (!didCopy) {
+ retCode = newCard->Copy(aData) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = ModifyCard(newCard) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ }
+ *aNewCard = newCard ;
+ NS_ADDREF(*aNewCard) ;
+ return retCode ;
+}
+
+static void UnicodeToWord(const char16_t *aUnicode, WORD& aWord)
+{
+ aWord = 0 ;
+ if (aUnicode == nullptr || *aUnicode == 0) { return ; }
+ nsresult errorCode = NS_OK;
+ nsAutoString unichar (aUnicode) ;
+
+ aWord = static_cast<WORD>(unichar.ToInteger(&errorCode));
+ if (NS_FAILED(errorCode)) {
+ PRINTF(("Error conversion string %S: %08x.\n", unichar.get(), errorCode)) ;
+ }
+}
+
+#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst"
+
+
+NS_IMETHODIMP nsAbOutlookDirectory::ModifyCard(nsIAbCard *aModifiedCard)
+{
+ NS_ENSURE_ARG_POINTER(aModifiedCard);
+
+ nsString *properties = nullptr;
+ nsAutoString utility;
+ nsAbWinHelperGuard mapiAddBook(mAbWinType);
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ nsCString entry;
+ nsresult retCode = ExtractCardEntry(aModifiedCard, entry);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ // If we don't have the card entry, we can't work.
+ if (entry.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ nsMapiEntry mapiData;
+ mapiData.Assign(entry);
+
+ // First, all the standard properties in one go
+ properties = new nsString[index_LastProp];
+ if (!properties) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aModifiedCard->GetFirstName(properties[index_FirstName]);
+ aModifiedCard->GetLastName(properties[index_LastName]);
+ // This triple search for something to put in the name
+ // is because in the case of a mailing list edition in
+ // Mozilla, the display name will not be provided, and
+ // MAPI doesn't allow that, so we fall back on an optional
+ // name, and when all fails, on the email address.
+ aModifiedCard->GetDisplayName(properties[index_DisplayName]);
+ if (properties[index_DisplayName].IsEmpty()) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t format;
+ rv = prefBranch->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &format);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aModifiedCard->GenerateName(format, nullptr,
+ properties[index_DisplayName]);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (properties[index_DisplayName].IsEmpty()) {
+ aModifiedCard->GetPrimaryEmail(properties[index_DisplayName]);
+ }
+ }
+ aModifiedCard->SetDisplayName(properties[index_DisplayName]);
+ aModifiedCard->GetPrimaryEmail(properties[index_EmailAddress]);
+ aModifiedCard->GetPropertyAsAString(kNicknameProperty, properties[index_NickName]);
+ aModifiedCard->GetPropertyAsAString(kWorkPhoneProperty, properties[index_WorkPhoneNumber]);
+ aModifiedCard->GetPropertyAsAString(kHomePhoneProperty, properties[index_HomePhoneNumber]);
+ aModifiedCard->GetPropertyAsAString(kFaxProperty, properties[index_WorkFaxNumber]);
+ aModifiedCard->GetPropertyAsAString(kPagerProperty, properties[index_PagerNumber]);
+ aModifiedCard->GetPropertyAsAString(kCellularProperty, properties[index_MobileNumber]);
+ aModifiedCard->GetPropertyAsAString(kHomeCityProperty, properties[index_HomeCity]);
+ aModifiedCard->GetPropertyAsAString(kHomeStateProperty, properties[index_HomeState]);
+ aModifiedCard->GetPropertyAsAString(kHomeZipCodeProperty, properties[index_HomeZip]);
+ aModifiedCard->GetPropertyAsAString(kHomeCountryProperty, properties[index_HomeCountry]);
+ aModifiedCard->GetPropertyAsAString(kWorkCityProperty, properties[index_WorkCity]);
+ aModifiedCard->GetPropertyAsAString(kWorkStateProperty, properties[index_WorkState]);
+ aModifiedCard->GetPropertyAsAString(kWorkZipCodeProperty, properties[index_WorkZip]);
+ aModifiedCard->GetPropertyAsAString(kWorkCountryProperty, properties[index_WorkCountry]);
+ aModifiedCard->GetPropertyAsAString(kJobTitleProperty, properties[index_JobTitle]);
+ aModifiedCard->GetPropertyAsAString(kDepartmentProperty, properties[index_Department]);
+ aModifiedCard->GetPropertyAsAString(kCompanyProperty, properties[index_Company]);
+ aModifiedCard->GetPropertyAsAString(kWorkWebPageProperty, properties[index_WorkWebPage]);
+ aModifiedCard->GetPropertyAsAString(kHomeWebPageProperty, properties[index_HomeWebPage]);
+ aModifiedCard->GetPropertyAsAString(kNotesProperty, properties[index_Comments]);
+ if (!mapiAddBook->SetPropertiesUString(mapiData, OutlookCardMAPIProps,
+ index_LastProp, properties)) {
+ PRINTF(("Cannot set general properties.\n")) ;
+ }
+
+ delete [] properties;
+ nsString unichar;
+ nsString unichar2;
+ WORD year = 0;
+ WORD month = 0;
+ WORD day = 0;
+
+ aModifiedCard->GetPropertyAsAString(kHomeAddressProperty, unichar);
+ aModifiedCard->GetPropertyAsAString(kHomeAddress2Property, unichar2);
+
+ utility.Assign(unichar.get());
+ if (!utility.IsEmpty())
+ utility.AppendLiteral("\r\n");
+
+ utility.Append(unichar2.get());
+ if (!mapiAddBook->SetPropertyUString(mapiData, PR_HOME_ADDRESS_STREET_W, utility.get())) {
+ PRINTF(("Cannot set home address.\n")) ;
+ }
+
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kWorkAddressProperty, unichar);
+ unichar2.Truncate();
+ aModifiedCard->GetPropertyAsAString(kWorkAddress2Property, unichar2);
+
+ utility.Assign(unichar.get());
+ if (!utility.IsEmpty())
+ utility.AppendLiteral("\r\n");
+
+ utility.Append(unichar2.get());
+ if (!mapiAddBook->SetPropertyUString(mapiData, PR_BUSINESS_ADDRESS_STREET_W, utility.get())) {
+ PRINTF(("Cannot set work address.\n")) ;
+ }
+
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kBirthYearProperty, unichar);
+ UnicodeToWord(unichar.get(), year);
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kBirthMonthProperty, unichar);
+ UnicodeToWord(unichar.get(), month);
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kBirthDayProperty, unichar);
+ UnicodeToWord(unichar.get(), day);
+ if (!mapiAddBook->SetPropertyDate(mapiData, PR_BIRTHDAY, year, month, day)) {
+ PRINTF(("Cannot set date.\n")) ;
+ }
+
+ return retCode;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::OnQueryFoundCard(nsIAbCard *aCard)
+{
+ return OnSearchFoundCard(aCard);
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::OnQueryResult(int32_t aResult,
+ int32_t aErrorCode)
+{
+ return OnSearchFinished(aResult, EmptyString());
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ return NS_OK;
+}
+
+static void splitString(nsString& aSource, nsString& aTarget)
+{
+ aTarget.Truncate();
+ int32_t offset = aSource.FindChar('\n');
+
+ if (offset >= 0)
+ {
+ const char16_t *source = aSource.get() + offset + 1;
+ while (*source)
+ {
+ if (*source == '\n' || *source == '\r')
+ aTarget.Append(char16_t(' '));
+ else
+ aTarget.Append(*source);
+ ++source;
+ }
+ aSource.SetLength(offset);
+ }
+}
+
+nsresult OutlookCardForURI(const nsACString &aUri, nsIAbCard **newCard)
+{
+ NS_ENSURE_ARG_POINTER(newCard);
+
+ nsAutoCString entry;
+ nsAutoCString stub;
+ uint32_t abWinType = getAbWinType(kOutlookCardScheme,
+ PromiseFlatCString(aUri).get(), stub, entry);
+ if (abWinType == nsAbWinType_Unknown)
+ {
+ PRINTF(("Huge problem URI=%s.\n", PromiseFlatCString(aUri).get()));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAbWinHelperGuard mapiAddBook(abWinType);
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbCard> card = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ card->SetPropertyAsAUTF8String("OutlookEntryURI", aUri);
+ card->SetLocalId(aUri);
+
+ nsMapiEntry mapiData;
+ mapiData.Assign(entry);
+
+ nsString unichars[index_LastProp];
+
+ if (mapiAddBook->GetPropertiesUString(mapiData, OutlookCardMAPIProps,
+ index_LastProp, unichars))
+ {
+ card->SetFirstName(unichars[index_FirstName]);
+ card->SetLastName(unichars[index_LastName]);
+ card->SetDisplayName(unichars[index_DisplayName]);
+ card->SetPrimaryEmail(unichars[index_EmailAddress]);
+ card->SetPropertyAsAString(kNicknameProperty, unichars[index_NickName]);
+ card->SetPropertyAsAString(kWorkPhoneProperty, unichars[index_WorkPhoneNumber]);
+ card->SetPropertyAsAString(kHomePhoneProperty, unichars[index_HomePhoneNumber]);
+ card->SetPropertyAsAString(kFaxProperty, unichars[index_WorkFaxNumber]);
+ card->SetPropertyAsAString(kPagerProperty, unichars[index_PagerNumber]);
+ card->SetPropertyAsAString(kCellularProperty, unichars[index_MobileNumber]);
+ card->SetPropertyAsAString(kHomeCityProperty, unichars[index_HomeCity]);
+ card->SetPropertyAsAString(kHomeStateProperty, unichars[index_HomeState]);
+ card->SetPropertyAsAString(kHomeZipCodeProperty, unichars[index_HomeZip]);
+ card->SetPropertyAsAString(kHomeCountryProperty, unichars[index_HomeCountry]);
+ card->SetPropertyAsAString(kWorkCityProperty, unichars[index_WorkCity]);
+ card->SetPropertyAsAString(kWorkStateProperty, unichars[index_WorkState]);
+ card->SetPropertyAsAString(kWorkZipCodeProperty, unichars[index_WorkZip]);
+ card->SetPropertyAsAString(kWorkCountryProperty, unichars[index_WorkCountry]);
+ card->SetPropertyAsAString(kJobTitleProperty, unichars[index_JobTitle]);
+ card->SetPropertyAsAString(kDepartmentProperty, unichars[index_Department]);
+ card->SetPropertyAsAString(kCompanyProperty, unichars[index_Company]);
+ card->SetPropertyAsAString(kWorkWebPageProperty, unichars[index_WorkWebPage]);
+ card->SetPropertyAsAString(kHomeWebPageProperty, unichars[index_HomeWebPage]);
+ card->SetPropertyAsAString(kNotesProperty, unichars[index_Comments]);
+ }
+
+ ULONG cardType = 0;
+ if (mapiAddBook->GetPropertyLong(mapiData, PR_OBJECT_TYPE, cardType))
+ {
+ card->SetIsMailList(cardType == MAPI_DISTLIST);
+ if (cardType == MAPI_DISTLIST)
+ {
+ nsAutoCString normalChars;
+ buildAbWinUri(kOutlookDirectoryScheme, abWinType, normalChars);
+ normalChars.Append(entry);
+ card->SetMailListURI(normalChars.get());
+ }
+ }
+
+ nsAutoString unichar;
+ nsAutoString unicharBis;
+ if (mapiAddBook->GetPropertyUString(mapiData, PR_HOME_ADDRESS_STREET_W, unichar))
+ {
+ splitString(unichar, unicharBis);
+ card->SetPropertyAsAString(kHomeAddressProperty, unichar);
+ card->SetPropertyAsAString(kHomeAddress2Property, unicharBis);
+ }
+ if (mapiAddBook->GetPropertyUString(mapiData, PR_BUSINESS_ADDRESS_STREET_W,
+ unichar))
+ {
+ splitString(unichar, unicharBis);
+ card->SetPropertyAsAString(kWorkAddressProperty, unichar);
+ card->SetPropertyAsAString(kWorkAddress2Property, unicharBis);
+ }
+
+ WORD year = 0, month = 0, day = 0;
+ if (mapiAddBook->GetPropertyDate(mapiData, PR_BIRTHDAY, year, month, day))
+ {
+ card->SetPropertyAsUint32(kBirthYearProperty, year);
+ card->SetPropertyAsUint32(kBirthMonthProperty, month);
+ card->SetPropertyAsUint32(kBirthDayProperty, day);
+ }
+
+ card.swap(*newCard);
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbOutlookDirectory.h b/mailnews/addrbook/src/nsAbOutlookDirectory.h
new file mode 100644
index 000000000..116966ce3
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOutlookDirectory.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsAbOutlookDirectory_h___
+#define nsAbOutlookDirectory_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbDirProperty.h"
+#include "nsIAbDirectoryQuery.h"
+#include "nsIAbDirectorySearch.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsDataHashtable.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIMutableArray.h"
+#include "nsAbWinHelper.h"
+
+struct nsMapiEntry ;
+
+class nsAbOutlookDirectory : public nsAbDirProperty, // nsIAbDirectory
+ public nsIAbDirectoryQuery,
+ public nsIAbDirectorySearch,
+ public nsIAbDirSearchListener,
+ public nsIAbDirectoryQueryResultListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIABDIRSEARCHLISTENER
+ NS_DECL_NSIABDIRECTORYQUERYRESULTLISTENER
+
+ nsAbOutlookDirectory(void);
+
+ // nsAbDirProperty methods
+ NS_IMETHOD GetDirType(int32_t *aDirType) override;
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+ NS_IMETHOD GetChildCards(nsISimpleEnumerator **aCards) override;
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator **aNodes) override;
+ NS_IMETHOD GetIsQuery(bool *aResult) override;
+ NS_IMETHOD HasCard(nsIAbCard *aCard, bool *aHasCard) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory *aDirectory, bool *aHasDirectory) override;
+ NS_IMETHOD DeleteCards(nsIArray *aCardList) override;
+ NS_IMETHOD DeleteDirectory(nsIAbDirectory *aDirectory) override;
+ NS_IMETHOD UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) override;
+ NS_IMETHOD AddCard(nsIAbCard *aData, nsIAbCard **addedCard) override;
+ NS_IMETHOD ModifyCard(nsIAbCard *aModifiedCard) override;
+ NS_IMETHOD DropCard(nsIAbCard *aData, bool needToCopyCard) override;
+ NS_IMETHOD AddMailList(nsIAbDirectory *aMailList, nsIAbDirectory **addedList) override;
+ NS_IMETHOD EditMailListToDatabase(nsIAbCard *listCard) override;
+
+ // nsAbDirProperty method
+ NS_IMETHOD Init(const char *aUri) override;
+ // nsIAbDirectoryQuery methods
+ NS_DECL_NSIABDIRECTORYQUERY
+ // nsIAbDirectorySearch methods
+ NS_DECL_NSIABDIRECTORYSEARCH
+ // Perform a MAPI query (function executed in a separate thread)
+ nsresult ExecuteQuery(SRestriction &aRestriction,
+ nsIAbDirSearchListener *aListener,
+ int32_t aResultLimit, int32_t aTimeout,
+ int32_t aThreadId);
+
+protected:
+ // Retrieve hierarchy as cards, with an optional restriction
+ nsresult GetChildCards(nsIMutableArray *aCards, void *aRestriction);
+ // Retrieve hierarchy as directories
+ nsresult GetChildNodes(nsIMutableArray *aNodes);
+ // Create a new card
+ nsresult CreateCard(nsIAbCard *aData, nsIAbCard **aNewCard);
+ // Notification for the UI
+ nsresult NotifyItemDeletion(nsISupports *aItem);
+ nsresult NotifyItemAddition(nsISupports *aItem);
+ // Force update of MAPI repository for mailing list
+ nsresult CommitAddressList(void);
+ // Read MAPI repository
+ nsresult UpdateAddressList(void);
+
+ nsMapiEntry *mMapiData;
+ // Container for the query threads
+ nsDataHashtable<nsUint32HashKey, PRThread*> mQueryThreads;
+ int32_t mCurrentQueryId;
+ PRLock *mProtector;
+ // Data for the search interfaces
+ nsInterfaceHashtable<nsISupportsHashKey, nsIAbCard> mCardList;
+ int32_t mSearchContext;
+ // Windows AB type
+ uint32_t mAbWinType;
+
+private:
+ virtual ~nsAbOutlookDirectory(void);
+
+};
+
+enum
+{
+ index_DisplayName = 0,
+ index_EmailAddress,
+ index_FirstName,
+ index_LastName,
+ index_NickName,
+ index_WorkPhoneNumber,
+ index_HomePhoneNumber,
+ index_WorkFaxNumber,
+ index_PagerNumber,
+ index_MobileNumber,
+ index_HomeCity,
+ index_HomeState,
+ index_HomeZip,
+ index_HomeCountry,
+ index_WorkCity,
+ index_WorkState,
+ index_WorkZip,
+ index_WorkCountry,
+ index_JobTitle,
+ index_Department,
+ index_Company,
+ index_WorkWebPage,
+ index_HomeWebPage,
+ index_Comments,
+ index_LastProp
+};
+
+static const ULONG OutlookCardMAPIProps[] =
+{
+ PR_DISPLAY_NAME_W,
+ PR_EMAIL_ADDRESS_W,
+ PR_GIVEN_NAME_W,
+ PR_SURNAME_W,
+ PR_NICKNAME_W,
+ PR_BUSINESS_TELEPHONE_NUMBER_W,
+ PR_HOME_TELEPHONE_NUMBER_W,
+ PR_BUSINESS_FAX_NUMBER_W,
+ PR_PAGER_TELEPHONE_NUMBER_W,
+ PR_MOBILE_TELEPHONE_NUMBER_W,
+ PR_HOME_ADDRESS_CITY_W,
+ PR_HOME_ADDRESS_STATE_OR_PROVINCE_W,
+ PR_HOME_ADDRESS_POSTAL_CODE_W,
+ PR_HOME_ADDRESS_COUNTRY_W,
+ PR_BUSINESS_ADDRESS_CITY_W,
+ PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W,
+ PR_BUSINESS_ADDRESS_POSTAL_CODE_W,
+ PR_BUSINESS_ADDRESS_COUNTRY_W,
+ PR_TITLE_W,
+ PR_DEPARTMENT_NAME_W,
+ PR_COMPANY_NAME_W,
+ PR_BUSINESS_HOME_PAGE_W,
+ PR_PERSONAL_HOME_PAGE_W,
+ PR_COMMENT_W
+};
+
+nsresult OutlookCardForURI(const nsACString &aUri, nsIAbCard **card);
+
+#endif // nsAbOutlookDirectory_h___
diff --git a/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp b/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp
new file mode 100644
index 000000000..fe1f22e00
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp
@@ -0,0 +1,337 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbQueryStringToExpression.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsITextToSubURI.h"
+#include "nsAbBooleanExpression.h"
+#include "nsAbBaseCID.h"
+#include "plstr.h"
+#include "nsIMutableArray.h"
+
+/**
+ * This code parses the query expression passed in as an addressbook URI.
+ * The expression takes the form:
+ * (BOOL1(FIELD1,OP1,VALUE1)..(FIELDn,OPn,VALUEn)(BOOL2(FIELD1,OP1,VALUE1)...)...)
+ *
+ * BOOLn A boolean operator joining subsequent terms delimited by ().
+ * For possible values see CreateBooleanExpression().
+ * FIELDn An addressbook card data field.
+ * OPn An operator for the search term.
+ * For possible values see CreateBooleanConditionString().
+ * VALUEn The value to be matched in the FIELDn via the OPn operator.
+ * The value must be URL encoded by the caller, if it contains any special
+ * characters including '(' and ')'.
+ */
+nsresult nsAbQueryStringToExpression::Convert (
+ const nsACString &aQueryString,
+ nsIAbBooleanExpression** expression)
+{
+ nsresult rv;
+
+ nsAutoCString q(aQueryString);
+ q.StripWhitespace();
+ const char *queryChars = q.get();
+
+ nsCOMPtr<nsISupports> s;
+ rv = ParseExpression(&queryChars, getter_AddRefs(s));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Case: Not end of string
+ if (*queryChars != 0)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbBooleanExpression> e(do_QueryInterface(s, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*expression = e);
+ return rv;
+}
+
+nsresult nsAbQueryStringToExpression::ParseExpression (
+ const char** index,
+ nsISupports** expression)
+{
+ nsresult rv;
+
+ if (**index != '(')
+ return NS_ERROR_FAILURE;
+
+ const char* indexBracket = *index + 1;
+ while (*indexBracket &&
+ *indexBracket != '(' && *indexBracket != ')')
+ indexBracket++;
+
+ // Case: End of string
+ if (*indexBracket == 0)
+ return NS_ERROR_FAILURE;
+
+ // Case: "((" or "()"
+ if (indexBracket == *index + 1)
+ {
+ return NS_ERROR_FAILURE;
+ }
+ // Case: "(*("
+ else if (*indexBracket == '(')
+ {
+ // printf ("Case: (*(: %s\n", *index);
+
+ nsCString operation;
+ rv = ParseOperationEntry (
+ *index, indexBracket,
+ getter_Copies (operation));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> e;
+ rv = CreateBooleanExpression(operation.get(),
+ getter_AddRefs(e));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Case: "(*)(*)....(*))"
+ *index = indexBracket;
+ rv = ParseExpressions (index, e);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*expression = e);
+ }
+ // Case" "(*)"
+ else if (*indexBracket == ')')
+ {
+ // printf ("Case: (*): %s\n", *index);
+
+ nsCOMPtr<nsIAbBooleanConditionString> conditionString;
+ rv = ParseCondition (index, indexBracket,
+ getter_AddRefs(conditionString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*expression = conditionString);
+ }
+
+ if (**index != ')')
+ return NS_ERROR_FAILURE;
+
+ (*index)++;
+
+ return NS_OK;
+}
+
+
+nsresult nsAbQueryStringToExpression::ParseExpressions (
+ const char** index,
+ nsIAbBooleanExpression* expression)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> expressions(do_CreateInstance(NS_ARRAY_CONTRACTID,
+ &rv));
+ if (NS_FAILED(rv))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Case: ")(*)(*)....(*))"
+ // printf ("Case: )(*)(*)....(*)): %s\n", *index);
+ while (**index == '(')
+ {
+ nsCOMPtr<nsISupports> childExpression;
+ rv = ParseExpression(index, getter_AddRefs (childExpression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ expressions->AppendElement(childExpression, false);
+ }
+
+ if (**index == 0)
+ return NS_ERROR_FAILURE;
+
+ // Case: "))"
+ // printf ("Case: )): %s\n", *index);
+
+ if (**index != ')')
+ return NS_ERROR_FAILURE;
+
+ expression->SetExpressions (expressions);
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseCondition (
+ const char** index,
+ const char* indexBracketClose,
+ nsIAbBooleanConditionString** conditionString)
+{
+ nsresult rv;
+
+ (*index)++;
+
+ nsCString entries[3];
+ for (int i = 0; i < 3; i++)
+ {
+ rv = ParseConditionEntry (index, indexBracketClose,
+ getter_Copies (entries[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*index == indexBracketClose)
+ break;
+ }
+
+ if (*index != indexBracketClose)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbBooleanConditionString> c;
+ rv = CreateBooleanConditionString (
+ entries[0].get(),
+ entries[1].get(),
+ entries[2].get(),
+ getter_AddRefs (c));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*conditionString = c);
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseConditionEntry (
+ const char** index,
+ const char* indexBracketClose,
+ char** entry)
+{
+ const char* indexDeliminator = *index;
+ while (indexDeliminator != indexBracketClose &&
+ *indexDeliminator != ',')
+ indexDeliminator++;
+
+ int entryLength = indexDeliminator - *index;
+ if (entryLength)
+ *entry = PL_strndup (*index, entryLength);
+ else
+ *entry = 0;
+
+ if (indexDeliminator != indexBracketClose)
+ *index = indexDeliminator + 1;
+ else
+ *index = indexDeliminator;
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseOperationEntry (
+ const char* indexBracketOpen1,
+ const char* indexBracketOpen2,
+ char** operation)
+{
+ int operationLength = indexBracketOpen2 - indexBracketOpen1 - 1;
+ if (operationLength)
+ *operation = PL_strndup (indexBracketOpen1 + 1,
+ operationLength);
+ else
+ *operation = 0;
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::CreateBooleanExpression(
+ const char* operation,
+ nsIAbBooleanExpression** expression)
+{
+ nsAbBooleanOperationType op;
+ if (PL_strcasecmp (operation, "and") == 0)
+ op = nsIAbBooleanOperationTypes::AND;
+ else if (PL_strcasecmp (operation, "or") == 0)
+ op = nsIAbBooleanOperationTypes::OR;
+ else if (PL_strcasecmp (operation, "not") == 0)
+ op = nsIAbBooleanOperationTypes::NOT;
+ else
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+
+ nsCOMPtr <nsIAbBooleanExpression> expr = do_CreateInstance(NS_BOOLEANEXPRESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*expression = expr);
+
+ rv = expr->SetOperation (op);
+ return rv;
+}
+
+nsresult nsAbQueryStringToExpression::CreateBooleanConditionString (
+ const char* attribute,
+ const char* condition,
+ const char* value,
+ nsIAbBooleanConditionString** conditionString)
+{
+ if (attribute == 0 || condition == 0 || value == 0)
+ return NS_ERROR_FAILURE;
+
+ nsAbBooleanConditionType c;
+
+ if (PL_strcasecmp (condition, "=") == 0)
+ c = nsIAbBooleanConditionTypes::Is;
+ else if (PL_strcasecmp (condition, "!=") == 0)
+ c = nsIAbBooleanConditionTypes::IsNot;
+ else if (PL_strcasecmp (condition, "lt") == 0)
+ c = nsIAbBooleanConditionTypes::LessThan;
+ else if (PL_strcasecmp (condition, "gt") == 0)
+ c = nsIAbBooleanConditionTypes::GreaterThan;
+ else if (PL_strcasecmp (condition, "bw") == 0)
+ c = nsIAbBooleanConditionTypes::BeginsWith;
+ else if (PL_strcasecmp (condition, "ew") == 0)
+ c = nsIAbBooleanConditionTypes::EndsWith;
+ else if (PL_strcasecmp (condition, "c")== 0)
+ c = nsIAbBooleanConditionTypes::Contains;
+ else if (PL_strcasecmp (condition, "!c") == 0)
+ c = nsIAbBooleanConditionTypes::DoesNotContain;
+ else if (PL_strcasecmp (condition, "~=") == 0)
+ c = nsIAbBooleanConditionTypes::SoundsLike;
+ else if (PL_strcasecmp (condition, "regex") == 0)
+ c = nsIAbBooleanConditionTypes::RegExp;
+ else
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbBooleanConditionString> cs = do_CreateInstance(NS_BOOLEANCONDITIONSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cs->SetCondition (c);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID,&rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString attributeUCS2;
+ nsString valueUCS2;
+
+ rv = textToSubURI->UnEscapeAndConvert("UTF-8",
+ attribute, getter_Copies(attributeUCS2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = textToSubURI->UnEscapeAndConvert("UTF-8",
+ value, getter_Copies(valueUCS2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF16toUTF8 attributeUTF8(attributeUCS2);
+
+ rv = cs->SetName (attributeUTF8.get ());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cs->SetValue(valueUCS2.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ NS_ConvertUTF8toUTF16 valueUCS2(value);
+
+ rv = cs->SetName (attribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cs->SetValue (valueUCS2.get ());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+
+ NS_IF_ADDREF(*conditionString = cs);
+ return NS_OK;
+}
+
+
diff --git a/mailnews/addrbook/src/nsAbQueryStringToExpression.h b/mailnews/addrbook/src/nsAbQueryStringToExpression.h
new file mode 100644
index 000000000..dfd8da0c5
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbQueryStringToExpression.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbQueryStringToExpression_h__
+#define nsAbQueryStringToExpression_h__
+
+#include "nsIAbBooleanExpression.h"
+
+class nsAbQueryStringToExpression
+{
+public:
+ static nsresult Convert (
+ const nsACString &aQueryString,
+ nsIAbBooleanExpression** expression);
+
+protected:
+ static nsresult ParseExpression (
+ const char** index,
+ nsISupports** expression);
+ static nsresult ParseExpressions (
+ const char** index,
+ nsIAbBooleanExpression* expression);
+ static nsresult ParseCondition (
+ const char** index,
+ const char* indexBracketClose,
+ nsIAbBooleanConditionString** conditionString);
+
+ static nsresult ParseConditionEntry (
+ const char** index,
+ const char* indexBracketClose,
+ char** entry);
+ static nsresult ParseOperationEntry (
+ const char* indexBracketOpen1,
+ const char* indexBracketOpen2,
+ char** operation);
+
+ static nsresult CreateBooleanExpression(
+ const char* operation,
+ nsIAbBooleanExpression** expression);
+ static nsresult CreateBooleanConditionString (
+ const char* attribute,
+ const char* condition,
+ const char* value,
+ nsIAbBooleanConditionString** conditionString);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbUtils.h b/mailnews/addrbook/src/nsAbUtils.h
new file mode 100644
index 000000000..d6b8915e0
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbUtils.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbUtils_h__
+#define nsAbUtils_h__
+
+#include "nsMemory.h"
+
+/*
+ * Wrapper class to automatically free an array of
+ * char* when class goes out of scope
+ */
+class CharPtrArrayGuard
+{
+public:
+ CharPtrArrayGuard (bool freeElements = true) :
+ mFreeElements (freeElements),
+ mArray (0),
+ mSize (0)
+ {
+ }
+
+ ~CharPtrArrayGuard ()
+ {
+ Free ();
+ }
+
+ char* operator[](int i)
+ {
+ return mArray[i];
+ }
+
+ uint32_t* GetSizeAddr(void)
+ {
+ return &mSize;
+ }
+
+ uint32_t GetSize(void)
+ {
+ return mSize;
+ }
+
+ char*** GetArrayAddr(void)
+ {
+ return &mArray;
+ }
+
+ const char** GetArray(void)
+ {
+ return (const char** ) mArray;
+ }
+
+public:
+
+private:
+ bool mFreeElements;
+ char **mArray;
+ uint32_t mSize;
+
+ void Free ()
+ {
+ if (!mArray)
+ return;
+
+ if (mFreeElements)
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mSize, mArray);
+ else
+ {
+ free(mArray);
+ }
+ }
+};
+
+/*
+ * Wrapper class to automatically free an array of
+ * char16_t* when class goes out of scope
+ */
+class PRUnicharPtrArrayGuard
+{
+public:
+ PRUnicharPtrArrayGuard (bool freeElements = true) :
+ mFreeElements (freeElements),
+ mArray (0),
+ mSize (0)
+ {
+ }
+
+ ~PRUnicharPtrArrayGuard ()
+ {
+ Free ();
+ }
+
+ char16_t* operator[](int i)
+ {
+ return mArray[i];
+ }
+
+ uint32_t* GetSizeAddr(void)
+ {
+ return &mSize;
+ }
+
+ uint32_t GetSize(void)
+ {
+ return mSize;
+ }
+
+ char16_t*** GetArrayAddr(void)
+ {
+ return &mArray;
+ }
+
+ const char16_t** GetArray(void)
+ {
+ return (const char16_t** ) mArray;
+ }
+
+public:
+
+private:
+ bool mFreeElements;
+ char16_t **mArray;
+ uint32_t mSize;
+ void Free ()
+ {
+ if (!mArray)
+ return;
+
+ if (mFreeElements)
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mSize, mArray);
+ else
+ {
+ free(mArray);
+ }
+ }
+};
+
+#endif /* nsAbUtils_h__ */
diff --git a/mailnews/addrbook/src/nsAbView.cpp b/mailnews/addrbook/src/nsAbView.cpp
new file mode 100644
index 000000000..77f3122df
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbView.cpp
@@ -0,0 +1,1451 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsAbView.h"
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsIAbCard.h"
+#include "nsILocale.h"
+#include "nsILocaleService.h"
+#include "prmem.h"
+#include "nsCollationCID.h"
+#include "nsIAbManager.h"
+#include "nsAbBaseCID.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITreeColumns.h"
+#include "nsCRTGlue.h"
+#include "nsIMutableArray.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIStringBundle.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsArrayUtils.h"
+#include "nsIAddrDatabase.h" // for kPriEmailColumn
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+#define CARD_NOT_FOUND -1
+#define ALL_ROWS -1
+
+#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst"
+#define PREF_MAIL_ADDR_BOOK_DISPLAYNAME_AUTOGENERATION "mail.addr_book.displayName.autoGeneration"
+#define PREF_MAIL_ADDR_BOOK_DISPLAYNAME_LASTNAMEFIRST "mail.addr_book.displayName.lastnamefirst"
+
+// Also, our default primary sort
+#define GENERATED_NAME_COLUMN_ID "GeneratedName"
+
+NS_IMPL_ISUPPORTS(nsAbView, nsIAbView, nsITreeView, nsIAbListener, nsIObserver)
+
+nsAbView::nsAbView() : mInitialized(false),
+ mIsAllDirectoryRootView(false),
+ mSuppressSelectionChange(false),
+ mSuppressCountChange(false),
+ mGeneratedNameFormat(0)
+{
+}
+
+nsAbView::~nsAbView()
+{
+ if (mInitialized) {
+ NS_ASSERTION(NS_SUCCEEDED(ClearView()), "failed to close view");
+ }
+}
+
+NS_IMETHODIMP nsAbView::ClearView()
+{
+ mDirectory = nullptr;
+ mAbViewListener = nullptr;
+ if (mTree)
+ mTree->SetView(nullptr);
+ mTree = nullptr;
+ mTreeSelection = nullptr;
+
+ if (mInitialized)
+ {
+ nsresult rv;
+ mInitialized = false;
+ nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = pbi->RemoveObserver(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = abManager->RemoveAddressBookListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ int32_t i = mCards.Length();
+ while(i-- > 0)
+ NS_ASSERTION(NS_SUCCEEDED(RemoveCardAt(i)), "remove card failed\n");
+
+ return NS_OK;
+}
+
+nsresult nsAbView::RemoveCardAt(int32_t row)
+{
+ nsresult rv;
+
+ AbCard *abcard = mCards.ElementAt(row);
+ NS_IF_RELEASE(abcard->card);
+ mCards.RemoveElementAt(row);
+ PR_FREEIF(abcard->primaryCollationKey);
+ PR_FREEIF(abcard->secondaryCollationKey);
+ PR_FREEIF(abcard);
+
+
+ // This needs to happen after we remove the card, as RowCountChanged() will call GetRowCount()
+ if (mTree) {
+ rv = mTree->RowCountChanged(row, -1);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (mAbViewListener && !mSuppressCountChange) {
+ rv = mAbViewListener->OnCountChanged(mCards.Length());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ return NS_OK;
+}
+
+nsresult nsAbView::SetGeneratedNameFormatFromPrefs()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return prefBranchInt->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &mGeneratedNameFormat);
+}
+
+nsresult nsAbView::Initialize()
+{
+ if (mInitialized)
+ return NS_OK;
+
+ mInitialized = true;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = abManager->AddAddressBookListener(this, nsIAbListener::all);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pbi->AddObserver(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, this, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mABBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED);
+
+ rv = stringBundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(mABBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return SetGeneratedNameFormatFromPrefs();
+}
+
+NS_IMETHODIMP nsAbView::SetView(nsIAbDirectory *aAddressBook,
+ nsIAbViewListener *aAbViewListener,
+ const nsAString &aSortColumn,
+ const nsAString &aSortDirection,
+ nsAString &aResult)
+{
+ // Ensure we are initialized
+ nsresult rv = Initialize();
+
+ mAbViewListener = nullptr;
+ if (mTree)
+ {
+ // Try and speed deletion of old cards by disconnecting the tree from us.
+ mTreeSelection->ClearSelection();
+ mTree->SetView(nullptr);
+ }
+
+ // Clear out old cards
+ int32_t i = mCards.Length();
+ while(i-- > 0)
+ {
+ rv = RemoveCardAt(i);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "remove card failed\n");
+ }
+
+ // We replace all cards so any sorting is no longer valid.
+ mSortColumn.AssignLiteral("");
+ mSortDirection.AssignLiteral("");
+
+ nsCString uri;
+ aAddressBook->GetURI(uri);
+ int32_t searchBegin = uri.FindChar('?');
+ nsCString searchQuery(Substring(uri, searchBegin));
+ // This is a special case, a workaround basically, to just have all ABs.
+ if (searchQuery.EqualsLiteral("?")) {
+ searchQuery.AssignLiteral("");
+ }
+
+ if (Substring(uri, 0, searchBegin).EqualsLiteral(kAllDirectoryRoot)) {
+ mIsAllDirectoryRootView = true;
+ // We have special request case to search all addressbooks, so we need
+ // to iterate over all addressbooks.
+ // Since the request is for all addressbooks, the URI must have been
+ // passed with an extra '?'. We still check it for sanity and trim it here.
+ if (searchQuery.Find("??") == 0)
+ searchQuery = Substring(searchQuery, 1);
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = abManager->GetDirectories(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ nsCOMPtr<nsISupports> support;
+ nsCOMPtr<nsIAbDirectory> directory;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
+ rv = enumerator->GetNext(getter_AddRefs(support));
+ NS_ENSURE_SUCCESS(rv, rv);
+ directory = do_QueryInterface(support, &rv);
+
+ // If, for some reason, we are unable to get a directory, we continue.
+ if (NS_FAILED(rv))
+ continue;
+
+ // Get appropriate directory with search query.
+ nsCString uri;
+ directory->GetURI(uri);
+ rv = abManager->GetDirectory(uri + searchQuery, getter_AddRefs(directory));
+ mDirectory = directory;
+ rv = EnumerateCards();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ mIsAllDirectoryRootView = false;
+ mDirectory = aAddressBook;
+ rv = EnumerateCards();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_NAMED_LITERAL_STRING(generatedNameColumnId, GENERATED_NAME_COLUMN_ID);
+
+ // See if the persisted sortColumn is valid.
+ // It may not be, if you migrated from older versions, or switched between
+ // a mozilla build and a commercial build, which have different columns.
+ nsAutoString actualSortColumn;
+ if (!generatedNameColumnId.Equals(aSortColumn) && mCards.Length()) {
+ nsIAbCard *card = mCards.ElementAt(0)->card;
+ nsString value;
+ // XXX todo
+ // Need to check if _Generic is valid. GetCardValue() will always return NS_OK for _Generic
+ // We're going to have to ask mDirectory if it is.
+ // It might not be. example: _ScreenName is valid in Netscape, but not Mozilla.
+ rv = GetCardValue(card, PromiseFlatString(aSortColumn).get(), value);
+ if (NS_FAILED(rv))
+ actualSortColumn = generatedNameColumnId;
+ else
+ actualSortColumn = aSortColumn;
+ }
+ else
+ actualSortColumn = aSortColumn;
+
+ rv = SortBy(actualSortColumn.get(), PromiseFlatString(aSortDirection).get(), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mAbViewListener = aAbViewListener;
+ if (mAbViewListener && !mSuppressCountChange) {
+ rv = mAbViewListener->OnCountChanged(mCards.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aResult = actualSortColumn;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetDirectory(nsIAbDirectory **aDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_IF_ADDREF(*aDirectory = mDirectory);
+ return NS_OK;
+}
+
+nsresult nsAbView::EnumerateCards()
+{
+ nsresult rv;
+ nsCOMPtr<nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr<nsIAbCard> card;
+
+ if (!mDirectory)
+ return NS_ERROR_UNEXPECTED;
+
+ rv = mDirectory->GetChildCards(getter_AddRefs(cardsEnumerator));
+ if (NS_SUCCEEDED(rv) && cardsEnumerator)
+ {
+ nsCOMPtr<nsISupports> item;
+ bool more;
+ while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more)
+ {
+ rv = cardsEnumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item);
+ // Malloc these from an arena
+ AbCard *abcard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard));
+ if (!abcard)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ abcard->card = card;
+ NS_IF_ADDREF(abcard->card);
+
+ // XXX todo
+ // Would it be better to do an insertion sort, than append and sort?
+ // XXX todo
+ // If we knew how many cards there was going to be
+ // we could allocate an array of the size,
+ // instead of growing and copying as we append.
+ DebugOnly<bool> didAppend = mCards.AppendElement(abcard);
+ NS_ASSERTION(didAppend, "failed to append card");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetRowCount(int32_t *aRowCount)
+{
+ *aRowCount = mCards.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetSelection(nsITreeSelection * *aSelection)
+{
+ NS_IF_ADDREF(*aSelection = mTreeSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::SetSelection(nsITreeSelection * aSelection)
+{
+ mTreeSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetRowProperties(int32_t index, nsAString& properties)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetCellProperties(int32_t row, nsITreeColumn* col, nsAString& properties)
+{
+ NS_ENSURE_TRUE(row >= 0, NS_ERROR_UNEXPECTED);
+
+ if (mCards.Length() <= (size_t)row)
+ return NS_OK;
+
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+ // "G" == "GeneratedName"
+ if (colID[0] != char16_t('G'))
+ return NS_OK;
+
+ nsIAbCard *card = mCards.ElementAt(row)->card;
+
+ bool isMailList;
+ nsresult rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isMailList)
+ properties.AssignLiteral("MailList");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetColumnProperties(nsITreeColumn* col, nsAString& properties)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::IsContainer(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::IsContainerOpen(int32_t index, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::IsContainerEmpty(int32_t index, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::IsSeparator(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::IsSorted(bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::CanDrop(int32_t index,
+ int32_t orientation,
+ nsIDOMDataTransfer *dataTransfer,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::Drop(int32_t row,
+ int32_t orientation,
+ nsIDOMDataTransfer *dataTransfer)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::GetParentIndex(int32_t rowIndex, int32_t *_retval)
+{
+ *_retval = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::GetLevel(int32_t index, int32_t *_retval)
+{
+ *_retval = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ return NS_OK;
+}
+
+nsresult nsAbView::GetCardValue(nsIAbCard *card, const char16_t *colID,
+ nsAString &_retval)
+{
+ if (nsString(colID).EqualsLiteral("addrbook")) {
+ nsCString dirID;
+ nsresult rv = card->GetDirectoryId(dirID);
+ if (NS_SUCCEEDED(rv))
+ CopyUTF8toUTF16(Substring(dirID, dirID.FindChar('&') + 1), _retval);
+
+ return rv;
+ }
+
+ // "G" == "GeneratedName", "_P" == "_PhoneticName"
+ // else, standard column (like PrimaryEmail and _AimScreenName)
+ if (colID[0] == char16_t('G'))
+ return card->GenerateName(mGeneratedNameFormat, mABBundle, _retval);
+
+ if (colID[0] == char16_t('_') && colID[1] == char16_t('P'))
+ // Use LN/FN order for the phonetic name
+ return card->GeneratePhoneticName(true, _retval);
+
+ if (!NS_strcmp(colID, u"ChatName"))
+ return card->GenerateChatName(_retval);
+
+ nsresult rv = card->GetPropertyAsAString(NS_ConvertUTF16toUTF8(colID).get(), _retval);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ rv = NS_OK;
+ _retval.Truncate();
+ }
+ return rv;
+}
+
+nsresult nsAbView::RefreshTree()
+{
+ nsresult rv;
+
+ // The PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST pref affects how the GeneratedName column looks.
+ // so if the GeneratedName is our primary or secondary sort,
+ // we need to resort.
+ // the same applies for kPhoneticNameColumn
+ //
+ // XXX optimize me
+ // PrimaryEmail is always the secondary sort, unless it is currently the
+ // primary sort. So, if PrimaryEmail is the primary sort,
+ // GeneratedName might be the secondary sort.
+ //
+ // One day, we can get fancy and remember what the secondary sort is.
+ // We do that, we can fix this code. At best, it will turn a sort into a invalidate.
+ //
+ // If neither the primary nor the secondary sorts are GeneratedName (or kPhoneticNameColumn),
+ // all we have to do is invalidate (to show the new GeneratedNames),
+ // but the sort will not change.
+ if (mSortColumn.EqualsLiteral(GENERATED_NAME_COLUMN_ID) ||
+ mSortColumn.EqualsLiteral(kPriEmailProperty) ||
+ mSortColumn.EqualsLiteral(kPhoneticNameColumn)) {
+ rv = SortBy(mSortColumn.get(), mSortDirection.get(), true);
+ }
+ else {
+ rv = InvalidateTree(ALL_ROWS);
+
+ // Although the selection hasn't changed, the card that is selected may need
+ // to be displayed differently, therefore pretend that the selection has
+ // changed to force that update.
+ SelectionChanged();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbView::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ NS_ENSURE_TRUE(row >= 0 && (size_t)row < mCards.Length(), NS_ERROR_UNEXPECTED);
+
+ nsIAbCard *card = mCards.ElementAt(row)->card;
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+ return GetCardValue(card, colID, _retval);
+}
+
+NS_IMETHODIMP nsAbView::SetTree(nsITreeBoxObject *tree)
+{
+ mTree = tree;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::ToggleOpenState(int32_t index)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::CycleHeader(nsITreeColumn* col)
+{
+ return NS_OK;
+}
+
+nsresult nsAbView::InvalidateTree(int32_t row)
+{
+ if (!mTree)
+ return NS_OK;
+
+ if (row == ALL_ROWS)
+ return mTree->Invalidate();
+ else
+ return mTree->InvalidateRow(row);
+}
+
+NS_IMETHODIMP nsAbView::SelectionChanged()
+{
+ if (mAbViewListener && !mSuppressSelectionChange) {
+ nsresult rv = mAbViewListener->OnSelectionChanged();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::CycleCell(int32_t row, nsITreeColumn* col)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::IsEditable(int32_t row, nsITreeColumn* col, bool* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::IsSelectable(int32_t row, nsITreeColumn* col, bool* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::PerformAction(const char16_t *action)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::PerformActionOnRow(const char16_t *action, int32_t row)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::GetCardFromRow(int32_t row, nsIAbCard **aCard)
+{
+ *aCard = nullptr;
+ NS_ENSURE_TRUE(row >= 0, NS_ERROR_UNEXPECTED);
+ if (mCards.Length() <= (size_t)row) {
+ return NS_OK;
+ }
+
+ AbCard *a = mCards.ElementAt(row);
+ if (!a)
+ return NS_OK;
+
+ NS_IF_ADDREF(*aCard = a->card);
+ return NS_OK;
+}
+
+#define DESCENDING_SORT_FACTOR -1
+#define ASCENDING_SORT_FACTOR 1
+
+typedef struct SortClosure
+{
+ const char16_t *colID;
+ int32_t factor;
+ nsAbView *abView;
+} SortClosure;
+
+static int
+inplaceSortCallback(const AbCard *card1, const AbCard *card2, SortClosure *closure)
+{
+ int32_t sortValue;
+
+ // If we are sorting the "PrimaryEmail", swap the collation keys, as the secondary is always the
+ // PrimaryEmail. Use the last primary key as the secondary key.
+ //
+ // "Pr" to distinguish "PrimaryEmail" from "PagerNumber"
+ if (closure->colID[0] == char16_t('P') && closure->colID[1] == char16_t('r')) {
+ sortValue = closure->abView->CompareCollationKeys(card1->secondaryCollationKey,card1->secondaryCollationKeyLen,card2->secondaryCollationKey,card2->secondaryCollationKeyLen);
+ if (sortValue)
+ return sortValue * closure->factor;
+ else
+ return closure->abView->CompareCollationKeys(card1->primaryCollationKey,card1->primaryCollationKeyLen,card2->primaryCollationKey,card2->primaryCollationKeyLen) * (closure->factor);
+ }
+ else {
+ sortValue = closure->abView->CompareCollationKeys(card1->primaryCollationKey,card1->primaryCollationKeyLen,card2->primaryCollationKey,card2->primaryCollationKeyLen);
+ if (sortValue)
+ return sortValue * (closure->factor);
+ else
+ return closure->abView->CompareCollationKeys(card1->secondaryCollationKey,card1->secondaryCollationKeyLen,card2->secondaryCollationKey,card2->secondaryCollationKeyLen) * (closure->factor);
+ }
+}
+
+static void SetSortClosure(const char16_t *sortColumn, const char16_t *sortDirection, nsAbView *abView, SortClosure *closure)
+{
+ closure->colID = sortColumn;
+
+ if (sortDirection && !NS_strcmp(sortDirection, u"descending"))
+ closure->factor = DESCENDING_SORT_FACTOR;
+ else
+ closure->factor = ASCENDING_SORT_FACTOR;
+
+ closure->abView = abView;
+ return;
+}
+
+class CardComparator
+{
+public:
+ void SetClosure(SortClosure *closure) { m_closure = closure; };
+
+ bool Equals(const AbCard *a, const AbCard *b) const {
+ return inplaceSortCallback(a, b, m_closure) == 0;
+ }
+ bool LessThan(const AbCard *a, const AbCard *b) const{
+ return inplaceSortCallback(a, b, m_closure) < 0;
+ }
+
+private:
+ SortClosure *m_closure;
+};
+
+NS_IMETHODIMP nsAbView::SortBy(const char16_t *colID, const char16_t *sortDir, bool aResort = false)
+{
+ nsresult rv;
+
+ int32_t count = mCards.Length();
+
+ nsAutoString sortColumn;
+ if (!colID)
+ sortColumn = NS_LITERAL_STRING(GENERATED_NAME_COLUMN_ID); // default sort column
+ else
+ sortColumn = colID;
+
+ nsAutoString sortDirection;
+ if (!sortDir)
+ sortDirection = NS_LITERAL_STRING("ascending"); // default direction
+ else
+ sortDirection = sortDir;
+
+ if (mSortColumn.Equals(sortColumn) && !aResort) {
+ if (mSortDirection.Equals(sortDir)) {
+ // If sortColumn and sortDirection are identical since the last call, do nothing.
+ return NS_OK;
+ } else {
+ // If we are sorting by how we are already sorted,
+ // and just the sort direction changes, just reverse.
+ int32_t halfPoint = count / 2;
+ for (int32_t i = 0; i < halfPoint; i++) {
+ // Swap the elements.
+ AbCard *ptr1 = mCards.ElementAt(i);
+ AbCard *ptr2 = mCards.ElementAt(count - i - 1);
+ mCards.ReplaceElementAt(i, ptr2);
+ mCards.ReplaceElementAt(count - i - 1, ptr1);
+ }
+ mSortDirection = sortDir;
+ }
+ }
+ else {
+ // Generate collation keys
+ for (int32_t i = 0; i < count; i++) {
+ AbCard *abcard = mCards.ElementAt(i);
+
+ rv = GenerateCollationKeysForCard(sortColumn.get(), abcard);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // We need to do full sort.
+ SortClosure closure;
+ SetSortClosure(sortColumn.get(), sortDirection.get(), this, &closure);
+
+ nsCOMPtr<nsIMutableArray> selectedCards = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetSelectedCards(selectedCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> indexCard;
+
+ if (mTreeSelection) {
+ int32_t currentIndex = -1;
+
+ rv = mTreeSelection->GetCurrentIndex(&currentIndex);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (currentIndex != -1) {
+ rv = GetCardFromRow(currentIndex, getter_AddRefs(indexCard));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+
+ CardComparator cardComparator;
+ cardComparator.SetClosure(&closure);
+ mCards.Sort(cardComparator);
+
+ rv = ReselectCards(selectedCards, indexCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSortColumn = sortColumn;
+ mSortDirection = sortDirection;
+ }
+
+ rv = InvalidateTree(ALL_ROWS);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+int32_t nsAbView::CompareCollationKeys(uint8_t *key1, uint32_t len1, uint8_t *key2, uint32_t len2)
+{
+ NS_ASSERTION(mCollationKeyGenerator, "no key generator");
+ if (!mCollationKeyGenerator)
+ return 0;
+
+ int32_t result;
+
+ nsresult rv = mCollationKeyGenerator->CompareRawSortKey(key1,len1,key2,len2,&result);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "key compare failed");
+ if (NS_FAILED(rv))
+ result = 0;
+ return result;
+}
+
+nsresult nsAbView::GenerateCollationKeysForCard(const char16_t *colID, AbCard *abcard)
+{
+ nsresult rv;
+ nsString value;
+
+ if (!mCollationKeyGenerator)
+ {
+ nsCOMPtr<nsILocaleService> localeSvc = do_GetService(NS_LOCALESERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILocale> locale;
+ rv = localeSvc->GetApplicationLocale(getter_AddRefs(locale));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsICollationFactory> factory = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = factory->CreateCollation(locale, getter_AddRefs(mCollationKeyGenerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = GetCardValue(abcard->card, colID, value);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ PR_FREEIF(abcard->primaryCollationKey);
+ rv = mCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive,
+ value, &(abcard->primaryCollationKey), &(abcard->primaryCollationKeyLen));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Hardcode email to be our secondary key. As we are doing this, just call
+ // the card's GetCardValue direct, rather than our own function which will
+ // end up doing the same as then we can save a bit of time.
+ rv = abcard->card->GetPrimaryEmail(value);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ PR_FREEIF(abcard->secondaryCollationKey);
+ rv = mCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive,
+ value, &(abcard->secondaryCollationKey), &(abcard->secondaryCollationKeyLen));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+// A helper method currently returns true if the directory is an LDAP.
+// We can tweak this to return true for all Remote Address Books where the
+// search is asynchronous.
+bool isDirectoryRemote(nsCOMPtr<nsIAbDirectory> aDir)
+{
+ nsCString uri;
+ aDir->GetURI(uri);
+ return (uri.Find("moz-abldapdirectory") != kNotFound);
+}
+
+// A helper method to get the query string for nsIAbDirectory.
+nsCString getQuery(nsCOMPtr<nsIAbDirectory> aDir)
+{
+ nsCString uri;
+ aDir->GetURI(uri);
+ int32_t searchBegin = uri.FindChar('?');
+ if (searchBegin == kNotFound)
+ return EmptyCString();
+
+ return nsCString(Substring(uri, searchBegin));
+}
+
+NS_IMETHODIMP nsAbView::OnItemAdded(nsISupports *parentDir, nsISupports *item)
+{
+ nsresult rv;
+ nsCOMPtr <nsIAbDirectory> directory = do_QueryInterface(parentDir, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isRemote = isDirectoryRemote(directory);
+ // If the search is performed on All Address books, its possible that the LDAP
+ // results start coming when mDirectory has changed (LDAP search works in an
+ // asynchronous manner).
+ // Since the listeners are being added to all nsAbView instances, we need to
+ // make sure that all the views aren't updated by the listeners.
+ bool isDirectoryQuery = false;
+ bool isMDirectoryQuery = false;
+ // See if current parent directory to which the item is added is a query
+ // directory.
+ directory->GetIsQuery(&isDirectoryQuery);
+ // Get the query string for the directory in Advanced AB Search window.
+ nsCString directoryQuery(getQuery(directory));
+ // See if the selected directory in Address book main window is a query
+ // directory.
+ mDirectory->GetIsQuery(&isMDirectoryQuery);
+ // Get the query string for the selected directory in the main AB window.
+ nsCString mDirectoryQuery(getQuery(mDirectory));
+ if ((mIsAllDirectoryRootView && isRemote &&
+ isDirectoryQuery && isMDirectoryQuery &&
+ directoryQuery.Equals(mDirectoryQuery)) ||
+ directory.get() == mDirectory.get()) {
+ nsCOMPtr <nsIAbCard> addedCard = do_QueryInterface(item);
+ if (addedCard) {
+ // Malloc these from an arena
+ AbCard *abcard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard));
+ if (!abcard)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ abcard->card = addedCard;
+ NS_IF_ADDREF(abcard->card);
+
+ rv = GenerateCollationKeysForCard(mSortColumn.get(), abcard);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t index;
+ rv = AddCard(abcard, false /* select card */, &index);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbView::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ if (nsDependentString(someData).EqualsLiteral(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST)) {
+ nsresult rv = SetGeneratedNameFormatFromPrefs();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = RefreshTree();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsAbView::AddCard(AbCard *abcard, bool selectCardAfterAdding, int32_t *index)
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG_POINTER(abcard);
+
+ *index = FindIndexForInsert(abcard);
+ mCards.InsertElementAt(*index, abcard);
+
+ // This needs to happen after we insert the card, as RowCountChanged() will call GetRowCount()
+ if (mTree)
+ rv = mTree->RowCountChanged(*index, 1);
+
+ // Checking for mTree here works around core bug 399227
+ if (selectCardAfterAdding && mTreeSelection && mTree) {
+ mTreeSelection->SetCurrentIndex(*index);
+ mTreeSelection->RangedSelect(*index, *index, false /* augment */);
+ }
+
+ if (mAbViewListener && !mSuppressCountChange) {
+ rv = mAbViewListener->OnCountChanged(mCards.Length());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ return rv;
+}
+
+int32_t nsAbView::FindIndexForInsert(AbCard *abcard)
+{
+ int32_t count = mCards.Length();
+ int32_t i;
+
+ SortClosure closure;
+ SetSortClosure(mSortColumn.get(), mSortDirection.get(), this, &closure);
+
+ // XXX todo
+ // Make this a binary search
+ for (i=0; i < count; i++) {
+ int32_t value = inplaceSortCallback(abcard, mCards.ElementAt(i), &closure);
+ // XXX Fix me, this is not right for both ascending and descending
+ if (value <= 0)
+ break;
+ }
+ return i;
+}
+
+NS_IMETHODIMP nsAbView::OnItemRemoved(nsISupports *parentDir, nsISupports *item)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsIAbDirectory> directory = do_QueryInterface(parentDir,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (directory.get() == mDirectory.get())
+ return RemoveCardAndSelectNextCard(item);
+
+ // The pointers aren't the same, are the URI strings similar? This is most
+ // likely the case if the current directory is a search on a directory.
+ nsCString currentURI;
+ rv = mDirectory->GetURI(currentURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If it is a search, it will have something like ?(or(PrimaryEmail...
+ // on the end of the string, so remove that before comparing
+ int32_t pos = currentURI.FindChar('?');
+ if (pos != -1)
+ currentURI.SetLength(pos);
+
+ nsCString notifiedURI;
+ rv = directory->GetURI(notifiedURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (currentURI.Equals(notifiedURI))
+ return RemoveCardAndSelectNextCard(item);
+
+ return NS_OK;
+}
+
+nsresult nsAbView::RemoveCardAndSelectNextCard(nsISupports *item)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item);
+ if (card) {
+ int32_t index = FindIndexForCard(card);
+ if (index != CARD_NOT_FOUND) {
+ bool selectNextCard = false;
+ if (mTreeSelection) {
+ int32_t selectedIndex;
+ // XXX todo
+ // Make sure it works if nothing selected
+ mTreeSelection->GetCurrentIndex(&selectedIndex);
+ if (index == selectedIndex)
+ selectNextCard = true;
+ }
+
+ rv = RemoveCardAt(index);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (selectNextCard) {
+ int32_t count = mCards.Length();
+ if (count && mTreeSelection) {
+ // If we deleted the last card, adjust so we select the new "last" card
+ if (index >= (count - 1)) {
+ index = count -1;
+ }
+ mTreeSelection->SetCurrentIndex(index);
+ mTreeSelection->RangedSelect(index, index, false /* augment */);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+int32_t nsAbView::FindIndexForCard(nsIAbCard *card)
+{
+ int32_t count = mCards.Length();
+ int32_t i;
+
+ // You can't implement the binary search here, as all you have is the nsIAbCard
+ // you might be here because one of the card properties has changed, and that property
+ // could be the collation key.
+ for (i=0; i < count; i++) {
+ AbCard *abcard = mCards.ElementAt(i);
+ bool equals;
+ nsresult rv = card->Equals(abcard->card, &equals);
+ if (NS_SUCCEEDED(rv) && equals) {
+ return i;
+ }
+ }
+ return CARD_NOT_FOUND;
+}
+
+NS_IMETHODIMP nsAbView::OnItemPropertyChanged(nsISupports *item, const char *property, const char16_t *oldValue, const char16_t *newValue)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item);
+ if (!card)
+ return NS_OK;
+
+ int32_t index = FindIndexForCard(card);
+ if (index == -1)
+ return NS_OK;
+
+ AbCard *oldCard = mCards.ElementAt(index);
+
+ // Malloc these from an arena
+ AbCard *newCard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard));
+ if (!newCard)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newCard->card = card;
+ NS_IF_ADDREF(newCard->card);
+
+ rv = GenerateCollationKeysForCard(mSortColumn.get(), newCard);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool cardWasSelected = false;
+
+ if (mTreeSelection) {
+ rv = mTreeSelection->IsSelected(index, &cardWasSelected);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (!CompareCollationKeys(newCard->primaryCollationKey,newCard->primaryCollationKeyLen,oldCard->primaryCollationKey,oldCard->primaryCollationKeyLen)
+ && CompareCollationKeys(newCard->secondaryCollationKey,newCard->secondaryCollationKeyLen,oldCard->secondaryCollationKey,oldCard->secondaryCollationKeyLen)) {
+ // No need to remove and add, since the collation keys haven't changed.
+ // Since they haven't changed, the card will sort to the same place.
+ // We just need to clean up what we allocated.
+ NS_IF_RELEASE(newCard->card);
+ if (newCard->primaryCollationKey)
+ free(newCard->primaryCollationKey);
+ if (newCard->secondaryCollationKey)
+ free(newCard->secondaryCollationKey);
+ PR_FREEIF(newCard);
+
+ // Still need to invalidate, as the other columns may have changed.
+ rv = InvalidateTree(index);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else {
+ mSuppressSelectionChange = true;
+ mSuppressCountChange = true;
+
+ // Remove the old card.
+ rv = RemoveCardAt(index);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "remove card failed\n");
+
+ // Add the card we created, and select it (to restore selection) if it was selected.
+ rv = AddCard(newCard, cardWasSelected /* select card */, &index);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "add card failed\n");
+
+ mSuppressSelectionChange = false;
+ mSuppressCountChange = false;
+
+ // Ensure restored selection is visible
+ if (cardWasSelected && mTree)
+ mTree->EnsureRowIsVisible(index);
+ }
+
+ // Although the selection hasn't changed, the card that is selected may need
+ // to be displayed differently, therefore pretend that the selection has
+ // changed to force that update.
+ if (cardWasSelected)
+ SelectionChanged();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::SelectAll()
+{
+ if (mTreeSelection && mTree) {
+ mTreeSelection->SelectAll();
+ mTree->Invalidate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetSortDirection(nsAString & aDirection)
+{
+ aDirection = mSortDirection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetSortColumn(nsAString & aColumn)
+{
+ aColumn = mSortColumn;
+ return NS_OK;
+}
+
+nsresult nsAbView::ReselectCards(nsIArray *aCards, nsIAbCard *aIndexCard)
+{
+ uint32_t count;
+ uint32_t i;
+
+ if (!mTreeSelection || !aCards)
+ return NS_OK;
+
+ nsresult rv = mTreeSelection->ClearSelection();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aCards->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we don't have any cards selected, nothing else to do.
+ if (!count)
+ return NS_OK;
+
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbCard> card = do_QueryElementAt(aCards, i);
+ if (card) {
+ int32_t index = FindIndexForCard(card);
+ if (index != CARD_NOT_FOUND) {
+ mTreeSelection->RangedSelect(index, index, true /* augment */);
+ }
+ }
+ }
+
+ // Reset the index card, and ensure it is visible.
+ if (aIndexCard) {
+ int32_t currentIndex = FindIndexForCard(aIndexCard);
+ rv = mTreeSelection->SetCurrentIndex(currentIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mTree) {
+ rv = mTree->EnsureRowIsVisible(currentIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::DeleteSelectedCards()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> cardsToDelete = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetSelectedCards(cardsToDelete);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mDirectory should not be null
+ // Bullet proof (and assert) to help figure out bug #127748
+ NS_ENSURE_TRUE(mDirectory, NS_ERROR_UNEXPECTED);
+
+ rv = mDirectory->DeleteCards(cardsToDelete);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+nsresult nsAbView::GetSelectedCards(nsCOMPtr<nsIMutableArray> &aSelectedCards)
+{
+ if (!mTreeSelection)
+ return NS_OK;
+
+ int32_t selectionCount;
+ nsresult rv = mTreeSelection->GetRangeCount(&selectionCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!selectionCount)
+ return NS_OK;
+
+ for (int32_t i = 0; i < selectionCount; i++)
+ {
+ int32_t startRange;
+ int32_t endRange;
+ rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ int32_t totalCards = mCards.Length();
+ if (startRange >= 0 && startRange < totalCards)
+ {
+ for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < totalCards; rangeIndex++) {
+ nsCOMPtr<nsIAbCard> abCard;
+ rv = GetCardFromRow(rangeIndex, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aSelectedCards->AppendElement(abCard, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::SwapFirstNameLastName()
+{
+ if (!mTreeSelection)
+ return NS_OK;
+
+ int32_t selectionCount;
+ nsresult rv = mTreeSelection->GetRangeCount(&selectionCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!selectionCount)
+ return NS_OK;
+
+ // Prepare for displayname generation
+ // No cache for pref and bundle since the swap operation is not executed frequently
+ bool displayNameAutoGeneration;
+ bool displayNameLastnamefirst = false;
+
+ nsCOMPtr<nsIPrefBranch> pPrefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pPrefBranchInt->GetBoolPref(PREF_MAIL_ADDR_BOOK_DISPLAYNAME_AUTOGENERATION, &displayNameAutoGeneration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (displayNameAutoGeneration)
+ {
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ rv = pPrefBranchInt->GetComplexValue(PREF_MAIL_ADDR_BOOK_DISPLAYNAME_LASTNAMEFIRST,
+ NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString str;
+ pls->ToString(getter_Copies(str));
+ displayNameLastnamefirst = str.EqualsLiteral("true");
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (int32_t i = 0; i < selectionCount; i++)
+ {
+ int32_t startRange;
+ int32_t endRange;
+ rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ int32_t totalCards = mCards.Length();
+ if (startRange >= 0 && startRange < totalCards)
+ {
+ for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < totalCards; rangeIndex++) {
+ nsCOMPtr<nsIAbCard> abCard;
+ rv = GetCardFromRow(rangeIndex, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Swap FN/LN
+ nsAutoString fn, ln;
+ abCard->GetFirstName(fn);
+ abCard->GetLastName(ln);
+ if (!fn.IsEmpty() || !ln.IsEmpty())
+ {
+ abCard->SetFirstName(ln);
+ abCard->SetLastName(fn);
+
+ // Generate display name using the new order
+ if (displayNameAutoGeneration &&
+ !fn.IsEmpty() && !ln.IsEmpty())
+ {
+ nsString dnLnFn;
+ nsString dnFnLn;
+ const char16_t *nameString[2];
+ const char16_t *formatString;
+
+ // The format should stays the same before/after we swap the names
+ formatString = displayNameLastnamefirst ?
+ u"lastFirstFormat" :
+ u"firstLastFormat";
+
+ // Generate both ln/fn and fn/ln combination since we need both later
+ // to check to see if the current display name was edited
+ // note that fn/ln still hold the values before the swap
+ nameString[0] = ln.get();
+ nameString[1] = fn.get();
+ rv = bundle->FormatStringFromName(formatString,
+ nameString, 2, getter_Copies(dnLnFn));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nameString[0] = fn.get();
+ nameString[1] = ln.get();
+ rv = bundle->FormatStringFromName(formatString,
+ nameString, 2, getter_Copies(dnFnLn));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current display name
+ nsAutoString dn;
+ rv = abCard->GetDisplayName(dn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Swap the display name if not edited
+ if (displayNameLastnamefirst)
+ {
+ if (dn.Equals(dnLnFn))
+ abCard->SetDisplayName(dnFnLn);
+ }
+ else
+ {
+ if (dn.Equals(dnFnLn))
+ abCard->SetDisplayName(dnLnFn);
+ }
+ }
+
+ // Swap phonetic names
+ rv = abCard->GetPropertyAsAString(kPhoneticFirstNameProperty, fn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = abCard->GetPropertyAsAString(kPhoneticLastNameProperty, ln);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!fn.IsEmpty() || !ln.IsEmpty())
+ {
+ abCard->SetPropertyAsAString(kPhoneticFirstNameProperty, ln);
+ abCard->SetPropertyAsAString(kPhoneticLastNameProperty, fn);
+ }
+ }
+ }
+ }
+ }
+ // Update the tree
+ // Re-sort if either generated or phonetic name is primary or secondary sort,
+ // otherwise invalidate to reflect the change
+ rv = RefreshTree();
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbView::GetSelectedAddresses(nsIArray **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> selectedCards = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetSelectedCards(selectedCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> addresses = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t count;
+ selectedCards->GetLength(&count);
+
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbCard> card(do_QueryElementAt(selectedCards, i, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isMailList;
+ card->GetIsMailList(&isMailList);
+ nsAutoString primaryEmail;
+ if (isMailList) {
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString mailListURI;
+ rv = card->GetMailListURI(getter_Copies(mailListURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> mailList;
+ rv = abManager->GetDirectory(mailListURI, getter_AddRefs(mailList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> mailListAddresses;
+ rv = mailList->GetAddressLists(getter_AddRefs(mailListAddresses));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t mailListCount = 0;
+ mailListAddresses->GetLength(&mailListCount);
+
+ for (uint32_t j = 0; j < mailListCount; j++) {
+ nsCOMPtr<nsIAbCard> mailListCard = do_QueryElementAt(mailListAddresses, j, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mailListCard->GetPrimaryEmail(primaryEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!primaryEmail.IsEmpty()) {
+ nsCOMPtr<nsISupportsString> supportsEmail(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ supportsEmail->SetData(primaryEmail);
+ addresses->AppendElement(supportsEmail, false);
+ }
+ }
+ }
+ else {
+ rv = card->GetPrimaryEmail(primaryEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!primaryEmail.IsEmpty()) {
+ nsCOMPtr<nsISupportsString> supportsEmail(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ supportsEmail->SetData(primaryEmail);
+ addresses->AppendElement(supportsEmail, false);
+ }
+ }
+ }
+
+ NS_IF_ADDREF(*_retval = addresses);
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbView.h b/mailnews/addrbook/src/nsAbView.h
new file mode 100644
index 000000000..f0a913082
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbView.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsAbView_H_
+#define _nsAbView_H_
+
+#include "nsISupports.h"
+#include "nsStringGlue.h"
+#include "nsIAbView.h"
+#include "nsITreeView.h"
+#include "nsITreeBoxObject.h"
+#include "nsITreeSelection.h"
+#include "nsTArray.h"
+#include "nsIAbDirectory.h"
+#include "nsIAtom.h"
+#include "nsICollation.h"
+#include "nsIAbListener.h"
+#include "nsIObserver.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsIStringBundle.h"
+
+typedef struct AbCard
+{
+ nsIAbCard *card;
+ uint32_t primaryCollationKeyLen;
+ uint32_t secondaryCollationKeyLen;
+ uint8_t *primaryCollationKey;
+ uint8_t *secondaryCollationKey;
+} AbCard;
+
+
+class nsAbView : public nsIAbView, public nsITreeView, public nsIAbListener, public nsIObserver
+{
+public:
+ nsAbView();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABVIEW
+ NS_DECL_NSITREEVIEW
+ NS_DECL_NSIABLISTENER
+ NS_DECL_NSIOBSERVER
+
+ int32_t CompareCollationKeys(uint8_t *key1, uint32_t len1, uint8_t *key2, uint32_t len2);
+
+private:
+ virtual ~nsAbView();
+ nsresult Initialize();
+ int32_t FindIndexForInsert(AbCard *abcard);
+ int32_t FindIndexForCard(nsIAbCard *card);
+ nsresult GenerateCollationKeysForCard(const char16_t *colID, AbCard *abcard);
+ nsresult InvalidateTree(int32_t row);
+ nsresult RemoveCardAt(int32_t row);
+ nsresult AddCard(AbCard *abcard, bool selectCardAfterAdding, int32_t *index);
+ nsresult RemoveCardAndSelectNextCard(nsISupports *item);
+ nsresult EnumerateCards();
+ nsresult SetGeneratedNameFormatFromPrefs();
+ nsresult GetSelectedCards(nsCOMPtr<nsIMutableArray> &aSelectedCards);
+ nsresult ReselectCards(nsIArray *aCards, nsIAbCard *aIndexCard);
+ nsresult GetCardValue(nsIAbCard *card, const char16_t *colID, nsAString &_retval);
+ nsresult RefreshTree();
+
+ nsCOMPtr<nsITreeBoxObject> mTree;
+ nsCOMPtr<nsITreeSelection> mTreeSelection;
+ nsCOMPtr <nsIAbDirectory> mDirectory;
+ nsTArray<AbCard*> mCards;
+ nsString mSortColumn;
+ nsString mSortDirection;
+ nsCOMPtr<nsICollation> mCollationKeyGenerator;
+ nsCOMPtr<nsIAbViewListener> mAbViewListener;
+ nsCOMPtr<nsIStringBundle> mABBundle;
+
+ bool mInitialized;
+ bool mIsAllDirectoryRootView;
+ bool mSuppressSelectionChange;
+ bool mSuppressCountChange;
+ int32_t mGeneratedNameFormat;
+};
+
+#endif /* _nsAbView_H_ */
diff --git a/mailnews/addrbook/src/nsAbWinHelper.cpp b/mailnews/addrbook/src/nsAbWinHelper.cpp
new file mode 100644
index 000000000..c5b9d131f
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbWinHelper.cpp
@@ -0,0 +1,1003 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#define INITGUID
+#define USES_IID_IMAPIProp
+#define USES_IID_IMAPIContainer
+#define USES_IID_IABContainer
+#define USES_IID_IMAPITable
+#define USES_IID_IDistList
+
+#include "nsAbWinHelper.h"
+#include "nsMapiAddressBook.h"
+#include "nsWabAddressBook.h"
+
+#include <mapiguid.h>
+
+#include "mozilla/Logging.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gAbWinHelperLog
+ = PR_NewLogModule("nsAbWinHelperLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(gAbWinHelperLog, mozilla::LogLevel::Debug, args)
+
+// Small utility to ensure release of all MAPI interfaces
+template <class tInterface> struct nsMapiInterfaceWrapper
+{
+ tInterface mInterface ;
+
+ nsMapiInterfaceWrapper(void) : mInterface(NULL) {}
+ ~nsMapiInterfaceWrapper(void) {
+ if (mInterface != NULL) { mInterface->Release() ; }
+ }
+ operator LPUNKNOWN *(void) { return reinterpret_cast<LPUNKNOWN *>(&mInterface) ; }
+ tInterface operator -> (void) const { return mInterface ; }
+ operator tInterface *(void) { return &mInterface ; }
+} ;
+
+static void assignEntryID(LPENTRYID& aTarget, LPENTRYID aSource, ULONG aByteCount)
+{
+ if (aTarget != NULL) {
+ delete [] (reinterpret_cast<LPBYTE>(aTarget)) ;
+ aTarget = NULL ;
+ }
+ if (aSource != NULL) {
+ aTarget = reinterpret_cast<LPENTRYID>(new BYTE [aByteCount]) ;
+ memcpy(aTarget, aSource, aByteCount) ;
+ }
+}
+
+nsMapiEntry::nsMapiEntry(void)
+: mByteCount(0), mEntryId(NULL)
+{
+ MOZ_COUNT_CTOR(nsMapiEntry) ;
+}
+
+nsMapiEntry::nsMapiEntry(ULONG aByteCount, LPENTRYID aEntryId)
+: mByteCount(0), mEntryId(NULL)
+{
+ Assign(aByteCount, aEntryId) ;
+ MOZ_COUNT_CTOR(nsMapiEntry) ;
+}
+
+nsMapiEntry::~nsMapiEntry(void)
+{
+ Assign(0, NULL) ;
+ MOZ_COUNT_DTOR(nsMapiEntry) ;
+}
+
+void nsMapiEntry::Assign(ULONG aByteCount, LPENTRYID aEntryId)
+{
+ assignEntryID(mEntryId, aEntryId, aByteCount) ;
+ mByteCount = aByteCount ;
+}
+
+static char UnsignedToChar(unsigned char aUnsigned)
+{
+ if (aUnsigned < 0xA) { return '0' + aUnsigned ; }
+ return 'A' + aUnsigned - 0xA ;
+}
+
+static char kBase64Encoding [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-." ;
+static const int kARank = 0 ;
+static const int kaRank = 26 ;
+static const int k0Rank = 52 ;
+static const unsigned char kMinusRank = 62 ;
+static const unsigned char kDotRank = 63 ;
+
+static void UnsignedToBase64(unsigned char *& aUnsigned,
+ ULONG aNbUnsigned, nsCString& aString)
+{
+ if (aNbUnsigned > 0) {
+ unsigned char remain0 = (*aUnsigned & 0x03) << 4 ;
+
+ aString.Append(kBase64Encoding [(*aUnsigned >> 2) & 0x3F]) ;
+ ++ aUnsigned ;
+ if (aNbUnsigned > 1) {
+ unsigned char remain1 = (*aUnsigned & 0x0F) << 2 ;
+
+ aString.Append(kBase64Encoding [remain0 | ((*aUnsigned >> 4) & 0x0F)]) ;
+ ++ aUnsigned ;
+ if (aNbUnsigned > 2) {
+ aString.Append(kBase64Encoding [remain1 | ((*aUnsigned >> 6) & 0x03)]) ;
+ aString.Append(kBase64Encoding [*aUnsigned & 0x3F]) ;
+ ++ aUnsigned ;
+ }
+ else {
+ aString.Append(kBase64Encoding [remain1]) ;
+ }
+ }
+ else {
+ aString.Append(kBase64Encoding [remain0]) ;
+ }
+ }
+}
+
+static unsigned char CharToUnsigned(char aChar)
+{
+ if (aChar >= '0' && aChar <= '9') { return static_cast<unsigned char>(aChar) - '0' ; }
+ return static_cast<unsigned char>(aChar) - 'A' + 0xA ;
+}
+
+// This function must return the rank in kBase64Encoding of the
+// character provided.
+static unsigned char Base64To6Bits(char aBase64)
+{
+ if (aBase64 >= 'A' && aBase64 <= 'Z') {
+ return static_cast<unsigned char>(aBase64 - 'A' + kARank) ;
+ }
+ if (aBase64 >= 'a' && aBase64 <= 'z') {
+ return static_cast<unsigned char>(aBase64 - 'a' + kaRank) ;
+ }
+ if (aBase64 >= '0' && aBase64 <= '9') {
+ return static_cast<unsigned char>(aBase64 - '0' + k0Rank) ;
+ }
+ if (aBase64 == '-') { return kMinusRank ; }
+ if (aBase64 == '.') { return kDotRank ; }
+ return 0 ;
+}
+
+static void Base64ToUnsigned(const char *& aBase64, uint32_t aNbBase64,
+ unsigned char *&aUnsigned)
+{
+ // By design of the encoding, we must have at least two characters to use
+ if (aNbBase64 > 1) {
+ unsigned char first6Bits = Base64To6Bits(*aBase64 ++) ;
+ unsigned char second6Bits = Base64To6Bits(*aBase64 ++) ;
+
+ *aUnsigned = first6Bits << 2 ;
+ *aUnsigned |= second6Bits >> 4 ;
+ ++ aUnsigned ;
+ if (aNbBase64 > 2) {
+ unsigned char third6Bits = Base64To6Bits(*aBase64 ++) ;
+
+ *aUnsigned = second6Bits << 4 ;
+ *aUnsigned |= third6Bits >> 2 ;
+ ++ aUnsigned ;
+ if (aNbBase64 > 3) {
+ unsigned char fourth6Bits = Base64To6Bits(*aBase64 ++) ;
+
+ *aUnsigned = third6Bits << 6 ;
+ *aUnsigned |= fourth6Bits ;
+ ++ aUnsigned ;
+ }
+ }
+ }
+}
+
+void nsMapiEntry::Assign(const nsCString& aString)
+{
+ Assign(0, NULL) ;
+ ULONG byteCount = aString.Length() / 4 * 3 ;
+
+ if ((aString.Length() & 0x03) != 0) {
+ byteCount += (aString.Length() & 0x03) - 1 ;
+ }
+ const char *currentSource = aString.get() ;
+ unsigned char *currentTarget = new unsigned char [byteCount] ;
+ uint32_t i = 0 ;
+
+ mByteCount = byteCount ;
+ mEntryId = reinterpret_cast<LPENTRYID>(currentTarget) ;
+ for (i = aString.Length() ; i >= 4 ; i -= 4) {
+ Base64ToUnsigned(currentSource, 4, currentTarget) ;
+ }
+ Base64ToUnsigned(currentSource, i, currentTarget) ;
+}
+
+void nsMapiEntry::ToString(nsCString& aString) const
+{
+ aString.Truncate() ;
+ ULONG i = 0 ;
+ unsigned char *currentSource = reinterpret_cast<unsigned char *>(mEntryId) ;
+
+ for (i = mByteCount ; i >= 3 ; i -= 3) {
+ UnsignedToBase64(currentSource, 3, aString) ;
+ }
+ UnsignedToBase64(currentSource, i, aString) ;
+}
+
+void nsMapiEntry::Dump(void) const
+{
+ PRINTF(("%d\n", mByteCount)) ;
+ for (ULONG i = 0 ; i < mByteCount ; ++ i) {
+ PRINTF(("%02X", (reinterpret_cast<unsigned char *>(mEntryId)) [i])) ;
+ }
+ PRINTF(("\n")) ;
+}
+
+nsMapiEntryArray::nsMapiEntryArray(void)
+: mEntries(NULL), mNbEntries(0)
+{
+ MOZ_COUNT_CTOR(nsMapiEntryArray) ;
+}
+
+nsMapiEntryArray::~nsMapiEntryArray(void)
+{
+ if (mEntries) { delete [] mEntries ; }
+ MOZ_COUNT_DTOR(nsMapiEntryArray) ;
+}
+
+void nsMapiEntryArray::CleanUp(void)
+{
+ if (mEntries != NULL) {
+ delete [] mEntries ;
+ mEntries = NULL ;
+ mNbEntries = 0 ;
+ }
+}
+
+using namespace mozilla;
+
+uint32_t nsAbWinHelper::mEntryCounter = 0;
+nsAutoPtr<mozilla::Mutex> nsAbWinHelper::mMutex;
+uint32_t nsAbWinHelper::mUseCount = 0;
+// There seems to be a deadlock/auto-destruction issue
+// in MAPI when multiple threads perform init/release
+// operations at the same time. So I've put a mutex
+// around both the initialize process and the destruction
+// one. I just hope the rest of the calls don't need the
+// same protection (MAPI is supposed to be thread-safe).
+
+nsAbWinHelper::nsAbWinHelper(void)
+: mAddressBook(NULL), mLastError(S_OK)
+{
+ if (!mUseCount++)
+ mMutex = new mozilla::Mutex("nsAbWinHelper.mMutex");
+
+ MOZ_COUNT_CTOR(nsAbWinHelper);
+}
+
+nsAbWinHelper::~nsAbWinHelper(void)
+{
+ if (!--mUseCount)
+ mMutex = nullptr;
+ MOZ_COUNT_DTOR(nsAbWinHelper) ;
+}
+
+BOOL nsAbWinHelper::GetFolders(nsMapiEntryArray& aFolders)
+{
+ aFolders.CleanUp() ;
+ nsMapiInterfaceWrapper<LPABCONT> rootFolder ;
+ nsMapiInterfaceWrapper<LPMAPITABLE> folders ;
+ ULONG objType = 0 ;
+ ULONG rowCount = 0 ;
+ SRestriction restriction ;
+ SPropTagArray folderColumns ;
+
+ mLastError = mAddressBook->OpenEntry(0, NULL, NULL, 0, &objType,
+ rootFolder) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open root %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = rootFolder->GetHierarchyTable(0, folders) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get hierarchy %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ // We only take into account modifiable containers,
+ // otherwise, we end up with all the directory services...
+ restriction.rt = RES_BITMASK ;
+ restriction.res.resBitMask.ulPropTag = PR_CONTAINER_FLAGS ;
+ restriction.res.resBitMask.relBMR = BMR_NEZ ;
+ restriction.res.resBitMask.ulMask = AB_MODIFIABLE ;
+ mLastError = folders->Restrict(&restriction, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot restrict table %08x.\n", mLastError)) ;
+ }
+ folderColumns.cValues = 1 ;
+ folderColumns.aulPropTag [0] = PR_ENTRYID ;
+ mLastError = folders->SetColumns(&folderColumns, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set columns %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = folders->GetRowCount(0, &rowCount) ;
+ if (HR_SUCCEEDED(mLastError)) {
+ aFolders.mEntries = new nsMapiEntry [rowCount] ;
+ aFolders.mNbEntries = 0 ;
+ do {
+ LPSRowSet rowSet = NULL ;
+
+ rowCount = 0 ;
+ mLastError = folders->QueryRows(1, 0, &rowSet) ;
+ if (HR_SUCCEEDED(mLastError)) {
+ rowCount = rowSet->cRows ;
+ if (rowCount > 0) {
+ nsMapiEntry& current = aFolders.mEntries [aFolders.mNbEntries ++] ;
+ SPropValue& currentValue = rowSet->aRow->lpProps [0] ;
+
+ current.Assign(currentValue.Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb)) ;
+ }
+ MyFreeProws(rowSet) ;
+ }
+ else {
+ PRINTF(("Cannot query rows %08x.\n", mLastError)) ;
+ }
+ } while (rowCount > 0) ;
+ }
+ return HR_SUCCEEDED(mLastError) ;
+}
+
+BOOL nsAbWinHelper::GetCards(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntryArray& aCards)
+{
+ aCards.CleanUp() ;
+ return GetContents(aParent, aRestriction, &aCards.mEntries, aCards.mNbEntries, 0) ;
+}
+
+BOOL nsAbWinHelper::GetNodes(const nsMapiEntry& aParent, nsMapiEntryArray& aNodes)
+{
+ aNodes.CleanUp() ;
+ return GetContents(aParent, NULL, &aNodes.mEntries, aNodes.mNbEntries, MAPI_DISTLIST) ;
+}
+
+BOOL nsAbWinHelper::GetCardsCount(const nsMapiEntry& aParent, ULONG& aNbCards)
+{
+ aNbCards = 0 ;
+ return GetContents(aParent, NULL, NULL, aNbCards, 0) ;
+}
+
+BOOL nsAbWinHelper::GetPropertyString(const nsMapiEntry& aObject,
+ ULONG aPropertyTag,
+ nsCString& aName)
+{
+ aName.Truncate() ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL) {
+ if (PROP_TYPE(values->ulPropTag) == PT_STRING8)
+ aName = values->Value.lpszA ;
+ else if (PROP_TYPE(values->ulPropTag) == PT_UNICODE)
+ aName = NS_LossyConvertUTF16toASCII(values->Value.lpszW);
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ nsString& aName)
+{
+ aName.Truncate() ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL) {
+ if (PROP_TYPE(values->ulPropTag) == PT_UNICODE)
+ aName = values->Value.lpszW ;
+ else if (PROP_TYPE(values->ulPropTag) == PT_STRING8)
+ aName.AssignASCII(values->Value.lpszA);
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertyTags,
+ ULONG aNbProperties, nsString *aNames)
+{
+ LPSPropValue values = NULL;
+ ULONG valueCount = 0;
+
+ if (!GetMAPIProperties(aObject, aPropertyTags, aNbProperties, values,
+ valueCount))
+ return FALSE;
+
+ if (valueCount == aNbProperties && values != NULL)
+ {
+ for (ULONG i = 0 ; i < valueCount ; ++ i)
+ {
+ if (PROP_ID(values[i].ulPropTag) == PROP_ID(aPropertyTags[i]))
+ {
+ if (PROP_TYPE(values[i].ulPropTag) == PT_STRING8)
+ aNames[i].AssignASCII(values[i].Value.lpszA);
+ else if (PROP_TYPE(values[i].ulPropTag) == PT_UNICODE)
+ aNames[i] = values[i].Value.lpszW;
+ }
+ }
+ FreeBuffer(values);
+ }
+ return TRUE;
+}
+
+BOOL nsAbWinHelper::GetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ WORD& aYear, WORD& aMonth, WORD& aDay)
+{
+ aYear = 0 ;
+ aMonth = 0 ;
+ aDay = 0 ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL && PROP_TYPE(values->ulPropTag) == PT_SYSTIME) {
+ SYSTEMTIME readableTime ;
+
+ if (FileTimeToSystemTime(&values->Value.ft, &readableTime)) {
+ aYear = readableTime.wYear ;
+ aMonth = readableTime.wMonth ;
+ aDay = readableTime.wDay ;
+ }
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetPropertyLong(const nsMapiEntry& aObject,
+ ULONG aPropertyTag,
+ ULONG& aValue)
+{
+ aValue = 0 ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL && PROP_TYPE(values->ulPropTag) == PT_LONG) {
+ aValue = values->Value.ul ;
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetPropertyBin(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ nsMapiEntry& aValue)
+{
+ aValue.Assign(0, NULL) ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL && PROP_TYPE(values->ulPropTag) == PT_BINARY) {
+ aValue.Assign(values->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(values->Value.bin.lpb)) ;
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+// This function, supposedly indicating whether a particular entry was
+// in a particular container, doesn't seem to work very well (has
+// a tendency to return TRUE even if we're talking to different containers...).
+BOOL nsAbWinHelper::TestOpenEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry)
+{
+ nsMapiInterfaceWrapper<LPMAPICONTAINER> container ;
+ nsMapiInterfaceWrapper<LPMAPIPROP> subObject ;
+ ULONG objType = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId,
+ &IID_IMAPIContainer, 0, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = container->OpenEntry(aEntry.mByteCount, aEntry.mEntryId,
+ NULL, 0, &objType, subObject) ;
+ return HR_SUCCEEDED(mLastError) ;
+}
+
+BOOL nsAbWinHelper::DeleteEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry)
+{
+ nsMapiInterfaceWrapper<LPABCONT> container ;
+ ULONG objType = 0 ;
+ SBinary entry ;
+ SBinaryArray entryArray ;
+
+ mLastError = mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId,
+ &IID_IABContainer, MAPI_MODIFY, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ entry.cb = aEntry.mByteCount ;
+ entry.lpb = reinterpret_cast<LPBYTE>(aEntry.mEntryId) ;
+ entryArray.cValues = 1 ;
+ entryArray.lpbin = &entry ;
+ mLastError = container->DeleteEntries(&entryArray, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot delete entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::SetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ const char16_t *aValue)
+{
+ SPropValue value ;
+ nsAutoCString alternativeValue ;
+
+ value.ulPropTag = aPropertyTag ;
+ if (PROP_TYPE(aPropertyTag) == PT_UNICODE) {
+ value.Value.lpszW = wwc(const_cast<char16_t *>(aValue)) ;
+ }
+ else if (PROP_TYPE(aPropertyTag) == PT_STRING8) {
+ alternativeValue = NS_LossyConvertUTF16toASCII(aValue);
+ value.Value.lpszA = const_cast<char *>(alternativeValue.get()) ;
+ }
+ else {
+ PRINTF(("Property %08x is not a string.\n", aPropertyTag)) ;
+ return TRUE ;
+ }
+ return SetMAPIProperties(aObject, 1, &value) ;
+}
+
+BOOL nsAbWinHelper::SetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertiesTag,
+ ULONG aNbProperties, nsString *aValues)
+{
+ LPSPropValue values = new SPropValue [aNbProperties] ;
+ if (!values)
+ return FALSE ;
+
+ ULONG i = 0 ;
+ ULONG currentValue = 0 ;
+ nsAutoCString alternativeValue ;
+ BOOL retCode = TRUE ;
+
+ for (i = 0 ; i < aNbProperties ; ++ i) {
+ values [currentValue].ulPropTag = aPropertiesTag [i] ;
+ if (PROP_TYPE(aPropertiesTag [i]) == PT_UNICODE) {
+ const wchar_t *value = aValues [i].get() ;
+ values [currentValue ++].Value.lpszW = const_cast<wchar_t *>(value) ;
+ }
+ else if (PROP_TYPE(aPropertiesTag [i]) == PT_STRING8) {
+ LossyCopyUTF16toASCII(aValues [i], alternativeValue);
+ char *av = strdup(alternativeValue.get()) ;
+ if (!av) {
+ retCode = FALSE ;
+ break ;
+ }
+ values [currentValue ++].Value.lpszA = av ;
+ }
+ }
+ if (retCode)
+ retCode = SetMAPIProperties(aObject, currentValue, values) ;
+ for (i = 0 ; i < currentValue ; ++ i) {
+ if (PROP_TYPE(aPropertiesTag [i]) == PT_STRING8) {
+ NS_Free(values [i].Value.lpszA) ;
+ }
+ }
+ delete [] values ;
+ return retCode ;
+}
+
+BOOL nsAbWinHelper::SetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ WORD aYear, WORD aMonth, WORD aDay)
+{
+ SPropValue value ;
+
+ value.ulPropTag = aPropertyTag ;
+ if (PROP_TYPE(aPropertyTag) == PT_SYSTIME) {
+ SYSTEMTIME readableTime ;
+
+ readableTime.wYear = aYear ;
+ readableTime.wMonth = aMonth ;
+ readableTime.wDay = aDay ;
+ readableTime.wDayOfWeek = 0 ;
+ readableTime.wHour = 0 ;
+ readableTime.wMinute = 0 ;
+ readableTime.wSecond = 0 ;
+ readableTime.wMilliseconds = 0 ;
+ if (SystemTimeToFileTime(&readableTime, &value.Value.ft)) {
+ return SetMAPIProperties(aObject, 1, &value) ;
+ }
+ return TRUE ;
+ }
+ return FALSE ;
+}
+
+BOOL nsAbWinHelper::CreateEntry(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry)
+{
+ nsMapiInterfaceWrapper<LPABCONT> container ;
+ ULONG objType = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId,
+ &IID_IABContainer, MAPI_MODIFY, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropTagArray property ;
+ LPSPropValue value = NULL ;
+ ULONG valueCount = 0 ;
+
+ property.cValues = 1 ;
+ property.aulPropTag [0] = PR_DEF_CREATE_MAILUSER ;
+ mLastError = container->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot obtain template %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ nsMapiInterfaceWrapper<LPMAPIPROP> newEntry ;
+
+ mLastError = container->CreateEntry(value->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(value->Value.bin.lpb),
+ CREATE_CHECK_DUP_LOOSE,
+ newEntry) ;
+ FreeBuffer(value) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot create new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropValue displayName ;
+ LPSPropProblemArray problems = NULL ;
+ nsAutoString tempName ;
+
+ displayName.ulPropTag = PR_DISPLAY_NAME_W ;
+ tempName.AssignLiteral("__MailUser__") ;
+ tempName.AppendInt(mEntryCounter ++) ;
+ const wchar_t *tempNameValue = tempName.get();
+ displayName.Value.lpszW = const_cast<wchar_t *>(tempNameValue) ;
+ mLastError = newEntry->SetProps(1, &displayName, &problems) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set temporary name %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ property.aulPropTag [0] = PR_ENTRYID ;
+ mLastError = newEntry->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot get entry id %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ aNewEntry.Assign(value->Value.bin.cb, reinterpret_cast<LPENTRYID>(value->Value.bin.lpb)) ;
+ FreeBuffer(value) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::CreateDistList(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry)
+{
+ nsMapiInterfaceWrapper<LPABCONT> container ;
+ ULONG objType = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId,
+ &IID_IABContainer, MAPI_MODIFY, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropTagArray property ;
+ LPSPropValue value = NULL ;
+ ULONG valueCount = 0 ;
+
+ property.cValues = 1 ;
+ property.aulPropTag [0] = PR_DEF_CREATE_DL ;
+ mLastError = container->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot obtain template %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ nsMapiInterfaceWrapper<LPMAPIPROP> newEntry ;
+
+ mLastError = container->CreateEntry(value->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(value->Value.bin.lpb),
+ CREATE_CHECK_DUP_LOOSE,
+ newEntry) ;
+ FreeBuffer(value) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot create new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropValue displayName ;
+ LPSPropProblemArray problems = NULL ;
+ nsAutoString tempName ;
+
+ displayName.ulPropTag = PR_DISPLAY_NAME_W ;
+ tempName.AssignLiteral("__MailList__") ;
+ tempName.AppendInt(mEntryCounter ++) ;
+ const wchar_t *tempNameValue = tempName.get() ;
+ displayName.Value.lpszW = const_cast<wchar_t *>(tempNameValue) ;
+ mLastError = newEntry->SetProps(1, &displayName, &problems) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set temporary name %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ property.aulPropTag [0] = PR_ENTRYID ;
+ mLastError = newEntry->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot get entry id %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ aNewEntry.Assign(value->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(value->Value.bin.lpb)) ;
+ FreeBuffer(value) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::CopyEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aSource,
+ nsMapiEntry& aTarget)
+{
+ nsMapiInterfaceWrapper<LPABCONT> container ;
+ ULONG objType = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId,
+ &IID_IABContainer, MAPI_MODIFY, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ nsMapiInterfaceWrapper<LPMAPIPROP> newEntry ;
+
+ mLastError = container->CreateEntry(aSource.mByteCount, aSource.mEntryId,
+ CREATE_CHECK_DUP_LOOSE, newEntry) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot create new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropTagArray property ;
+ LPSPropValue value = NULL ;
+ ULONG valueCount = 0 ;
+
+ property.cValues = 1 ;
+ property.aulPropTag [0] = PR_ENTRYID ;
+ mLastError = newEntry->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot get entry id %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ aTarget.Assign(value->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(value->Value.bin.lpb)) ;
+ FreeBuffer(value) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetDefaultContainer(nsMapiEntry& aContainer)
+{
+ LPENTRYID entryId = NULL ;
+ ULONG byteCount = 0 ;
+
+ mLastError = mAddressBook->GetPAB(&byteCount, &entryId) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get PAB %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ aContainer.Assign(byteCount, entryId) ;
+ FreeBuffer(entryId) ;
+ return TRUE ;
+}
+
+enum
+{
+ ContentsColumnEntryId = 0,
+ ContentsColumnObjectType,
+ ContentsColumnsSize
+} ;
+
+static const SizedSPropTagArray(ContentsColumnsSize, ContentsColumns) =
+{
+ ContentsColumnsSize,
+ {
+ PR_ENTRYID,
+ PR_OBJECT_TYPE
+ }
+} ;
+
+BOOL nsAbWinHelper::GetContents(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntry **aList, ULONG& aNbElements, ULONG aMapiType)
+{
+ if (aList != NULL) { *aList = NULL ; }
+ aNbElements = 0 ;
+ nsMapiInterfaceWrapper<LPMAPICONTAINER> parent ;
+ nsMapiInterfaceWrapper<LPMAPITABLE> contents ;
+ ULONG objType = 0 ;
+ ULONG rowCount = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId,
+ &IID_IMAPIContainer, 0, &objType,
+ parent) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open parent %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ // Here, flags for WAB and MAPI could be different, so this works
+ // only as long as we don't want to use any flag in GetContentsTable
+ mLastError = parent->GetContentsTable(0, contents) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get contents %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ if (aRestriction != NULL) {
+ mLastError = contents->Restrict(aRestriction, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set restriction %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ }
+ mLastError = contents->SetColumns((LPSPropTagArray) &ContentsColumns, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set columns %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = contents->GetRowCount(0, &rowCount) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get result count %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ if (aList != NULL) { *aList = new nsMapiEntry [rowCount] ; }
+ aNbElements = 0 ;
+ do {
+ LPSRowSet rowSet = NULL ;
+
+ rowCount = 0 ;
+ mLastError = contents->QueryRows(1, 0, &rowSet) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot query rows %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ rowCount = rowSet->cRows ;
+ if (rowCount > 0 &&
+ (aMapiType == 0 ||
+ rowSet->aRow->lpProps[ContentsColumnObjectType].Value.ul == aMapiType)) {
+ if (aList != NULL) {
+ nsMapiEntry& current = (*aList) [aNbElements] ;
+ SPropValue& currentValue = rowSet->aRow->lpProps[ContentsColumnEntryId] ;
+
+ current.Assign(currentValue.Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb)) ;
+ }
+ ++ aNbElements ;
+ }
+ MyFreeProws(rowSet) ;
+ } while (rowCount > 0) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetMAPIProperties(const nsMapiEntry& aObject, const ULONG *aPropertyTags,
+ ULONG aNbProperties, LPSPropValue& aValue,
+ ULONG& aValueCount)
+{
+ nsMapiInterfaceWrapper<LPMAPIPROP> object ;
+ ULONG objType = 0 ;
+ LPSPropTagArray properties = NULL ;
+ ULONG i = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aObject.mByteCount, aObject.mEntryId,
+ &IID_IMAPIProp, 0, &objType,
+ object) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ AllocateBuffer(CbNewSPropTagArray(aNbProperties),
+ reinterpret_cast<void **>(&properties)) ;
+ properties->cValues = aNbProperties ;
+ for (i = 0 ; i < aNbProperties ; ++ i) {
+ properties->aulPropTag [i] = aPropertyTags [i] ;
+ }
+ mLastError = object->GetProps(properties, 0, &aValueCount, &aValue) ;
+ FreeBuffer(properties) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get props %08x.\n", mLastError)) ;
+ }
+ return HR_SUCCEEDED(mLastError) ;
+}
+
+BOOL nsAbWinHelper::SetMAPIProperties(const nsMapiEntry& aObject, ULONG aNbProperties,
+ const LPSPropValue& aValues)
+{
+ nsMapiInterfaceWrapper<LPMAPIPROP> object ;
+ ULONG objType = 0 ;
+ LPSPropProblemArray problems = NULL ;
+
+ mLastError = mAddressBook->OpenEntry(aObject.mByteCount, aObject.mEntryId,
+ &IID_IMAPIProp, MAPI_MODIFY, &objType,
+ object) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = object->SetProps(aNbProperties, aValues, &problems) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot update the object %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ if (problems != NULL) {
+ for (ULONG i = 0 ; i < problems->cProblem ; ++ i) {
+ PRINTF(("Problem %d: index %d code %08x.\n", i,
+ problems->aProblem [i].ulIndex,
+ problems->aProblem [i].scode)) ;
+ }
+ }
+ mLastError = object->SaveChanges(0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit changes %08x.\n", mLastError)) ;
+ }
+ return HR_SUCCEEDED(mLastError) ;
+}
+
+void nsAbWinHelper::MyFreeProws(LPSRowSet aRowset)
+{
+ if (aRowset == NULL) { return ; }
+ ULONG i = 0 ;
+
+ for (i = 0 ; i < aRowset->cRows ; ++ i) {
+ FreeBuffer(aRowset->aRow [i].lpProps) ;
+ }
+ FreeBuffer(aRowset) ;
+}
+
+nsAbWinHelperGuard::nsAbWinHelperGuard(uint32_t aType)
+: mHelper(NULL)
+{
+ switch(aType) {
+ case nsAbWinType_Outlook: mHelper = new nsMapiAddressBook ; break ;
+ case nsAbWinType_OutlookExp: mHelper = new nsWabAddressBook ; break ;
+ default: break ;
+ }
+}
+
+nsAbWinHelperGuard::~nsAbWinHelperGuard(void)
+{
+ delete mHelper ;
+}
+
+const char *kOutlookDirectoryScheme = "moz-aboutlookdirectory://" ;
+const int kOutlookDirSchemeLength = 21 ;
+const char *kOutlookStub = "op/" ;
+const int kOutlookStubLength = 3 ;
+const char *kOutlookExpStub = "oe/" ;
+const int kOutlookExpStubLength = 3 ;
+const char *kOutlookCardScheme = "moz-aboutlookcard://" ;
+const int kOutlookCardSchemeLength = 16 ;
+
+nsAbWinType getAbWinType(const char *aScheme, const char *aUri, nsCString& aStub, nsCString& aEntry)
+{
+ aStub.Truncate() ;
+ aEntry.Truncate() ;
+ uint32_t schemeLength = strlen(aScheme) ;
+
+ if (strncmp(aUri, aScheme, schemeLength) == 0) {
+ if (strncmp(aUri + schemeLength, kOutlookStub, kOutlookStubLength) == 0) {
+ aEntry = aUri + schemeLength + kOutlookStubLength ;
+ aStub = kOutlookStub ;
+ return nsAbWinType_Outlook ;
+ }
+ if (strncmp(aUri + schemeLength, kOutlookExpStub, kOutlookExpStubLength) == 0) {
+ aEntry = aUri + schemeLength + kOutlookExpStubLength ;
+ aStub = kOutlookExpStub ;
+ return nsAbWinType_OutlookExp ;
+ }
+ }
+ return nsAbWinType_Unknown ;
+}
+
+void buildAbWinUri(const char *aScheme, uint32_t aType, nsCString& aUri)
+{
+ aUri.Assign(aScheme) ;
+ switch(aType) {
+ case nsAbWinType_Outlook: aUri.Append(kOutlookStub) ; break ;
+ case nsAbWinType_OutlookExp: aUri.Append(kOutlookExpStub) ; break ;
+ default: aUri.Assign("") ;
+ }
+}
+
+
+
+
+
+
diff --git a/mailnews/addrbook/src/nsAbWinHelper.h b/mailnews/addrbook/src/nsAbWinHelper.h
new file mode 100644
index 000000000..fb0ad7e44
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbWinHelper.h
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsAbWinHelper_h___
+#define nsAbWinHelper_h___
+
+#include <windows.h>
+#include <mapix.h>
+
+#include "nsStringGlue.h"
+#include "mozilla/Mutex.h"
+#include "nsAutoPtr.h"
+
+struct nsMapiEntry
+{
+ ULONG mByteCount ;
+ LPENTRYID mEntryId ;
+
+ nsMapiEntry(void) ;
+ ~nsMapiEntry(void) ;
+ nsMapiEntry(ULONG aByteCount, LPENTRYID aEntryId) ;
+
+ void Assign(ULONG aByteCount, LPENTRYID aEntryId) ;
+ void Assign(const nsCString& aString) ;
+ void ToString(nsCString& aString) const ;
+ void Dump(void) const ;
+} ;
+
+struct nsMapiEntryArray
+{
+ nsMapiEntry *mEntries ;
+ ULONG mNbEntries ;
+
+ nsMapiEntryArray(void) ;
+ ~nsMapiEntryArray(void) ;
+
+ const nsMapiEntry& operator [] (int aIndex) const { return mEntries [aIndex] ; }
+ void CleanUp(void) ;
+} ;
+
+class nsAbWinHelper
+{
+public:
+ nsAbWinHelper(void) ;
+ virtual ~nsAbWinHelper(void) ;
+
+ // Get the top address books
+ BOOL GetFolders(nsMapiEntryArray& aFolders) ;
+ // Get a list of entries for cards/mailing lists in a folder/mailing list
+ BOOL GetCards(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntryArray& aCards) ;
+ // Get a list of mailing lists in a folder
+ BOOL GetNodes(const nsMapiEntry& aParent, nsMapiEntryArray& aNodes) ;
+ // Get the number of cards/mailing lists in a folder/mailing list
+ BOOL GetCardsCount(const nsMapiEntry& aParent, ULONG& aNbCards) ;
+ // Access last MAPI error
+ HRESULT LastError(void) const { return mLastError ; }
+ // Get the value of a MAPI property of type string
+ BOOL GetPropertyString(const nsMapiEntry& aObject, ULONG aPropertyTag, nsCString& aValue) ;
+ // Same as previous, but string is returned as unicode
+ BOOL GetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag, nsString& aValue) ;
+ // Get multiple string MAPI properties in one call.
+ BOOL GetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertiesTag,
+ ULONG aNbProperties, nsString *aValues);
+ // Get the value of a MAPI property of type SYSTIME
+ BOOL GetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ WORD& aYear, WORD& aMonth, WORD& aDay) ;
+ // Get the value of a MAPI property of type LONG
+ BOOL GetPropertyLong(const nsMapiEntry& aObject, ULONG aPropertyTag, ULONG& aValue) ;
+ // Get the value of a MAPI property of type BIN
+ BOOL GetPropertyBin(const nsMapiEntry& aObject, ULONG aPropertyTag, nsMapiEntry& aValue) ;
+ // Tests if a container contains an entry
+ BOOL TestOpenEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry) ;
+ // Delete an entry in the address book
+ BOOL DeleteEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry) ;
+ // Set the value of a MAPI property of type string in unicode
+ BOOL SetPropertyUString (const nsMapiEntry& aObject, ULONG aPropertyTag,
+ const char16_t *aValue) ;
+ // Same as previous, but with a bunch of properties in one call
+ BOOL SetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertiesTag,
+ ULONG aNbProperties, nsString *aValues) ;
+ // Set the value of a MAPI property of type SYSTIME
+ BOOL SetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ WORD aYear, WORD aMonth, WORD aDay) ;
+ // Create entry in the address book
+ BOOL CreateEntry(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry) ;
+ // Create a distribution list in the address book
+ BOOL CreateDistList(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry) ;
+ // Copy an existing entry in the address book
+ BOOL CopyEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aSource, nsMapiEntry& aTarget) ;
+ // Get a default address book container
+ BOOL GetDefaultContainer(nsMapiEntry& aContainer) ;
+ // Is the helper correctly initialised?
+ BOOL IsOK(void) const { return mAddressBook != NULL ; }
+
+protected:
+ HRESULT mLastError ;
+ LPADRBOOK mAddressBook ;
+ static uint32_t mEntryCounter ;
+ static uint32_t mUseCount ;
+ static nsAutoPtr<mozilla::Mutex> mMutex ;
+
+ // Retrieve the contents of a container, with an optional restriction
+ BOOL GetContents(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntry **aList, ULONG &aNbElements, ULONG aMapiType) ;
+ // Retrieve the values of a set of properties on a MAPI object
+ BOOL GetMAPIProperties(const nsMapiEntry& aObject, const ULONG *aPropertyTags,
+ ULONG aNbProperties,
+ LPSPropValue& aValues, ULONG& aValueCount) ;
+ // Set the values of a set of properties on a MAPI object
+ BOOL SetMAPIProperties(const nsMapiEntry& aObject, ULONG aNbProperties,
+ const LPSPropValue& aValues) ;
+ // Clean-up a rowset returned by QueryRows
+ void MyFreeProws(LPSRowSet aSet) ;
+ // Allocation of a buffer for transmission to interfaces
+ virtual void AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) = 0 ;
+ // Destruction of a buffer provided by the interfaces
+ virtual void FreeBuffer(LPVOID aBuffer) = 0 ;
+
+private:
+} ;
+
+enum nsAbWinType
+{
+ nsAbWinType_Unknown,
+ nsAbWinType_Outlook,
+ nsAbWinType_OutlookExp
+} ;
+
+class nsAbWinHelperGuard
+{
+public :
+ nsAbWinHelperGuard(uint32_t aType) ;
+ ~nsAbWinHelperGuard(void) ;
+
+ nsAbWinHelper *operator ->(void) { return mHelper ; }
+
+private:
+ nsAbWinHelper *mHelper ;
+} ;
+
+extern const char *kOutlookDirectoryScheme ;
+extern const int kOutlookDirSchemeLength ;
+extern const char *kOutlookStub ;
+extern const char *kOutlookExpStub ;
+extern const char *kOutlookCardScheme ;
+
+nsAbWinType getAbWinType(const char *aScheme, const char *aUri,
+ nsCString& aStub, nsCString& aEntry) ;
+void buildAbWinUri(const char *aScheme, uint32_t aType, nsCString& aUri) ;
+
+#endif // nsAbWinHelper_h___
+
+
+
diff --git a/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp b/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp
new file mode 100644
index 000000000..2d1fab36e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp
@@ -0,0 +1,323 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "msgCore.h" // precompiled header...
+#include "nsStringGlue.h"
+#include "nsIIOService.h"
+
+#include "nsIStreamListener.h"
+#include "nsAddbookProtocolHandler.h"
+
+#include "nsAddbookUrl.h"
+#include "nsAddbookProtocolHandler.h"
+#include "nsCOMPtr.h"
+#include "nsAbBaseCID.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbManager.h"
+#include "prmem.h"
+#include "nsIAbView.h"
+#include "nsITreeView.h"
+#include "nsIStringBundle.h"
+#include "nsIServiceManager.h"
+#include "mozilla/Services.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIPipe.h"
+#include "nsIPrincipal.h"
+
+nsAddbookProtocolHandler::nsAddbookProtocolHandler()
+{
+ mAddbookOperation = nsIAddbookUrlOperation::InvalidUrl;
+}
+
+nsAddbookProtocolHandler::~nsAddbookProtocolHandler()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAddbookProtocolHandler, nsIProtocolHandler)
+
+NS_IMETHODIMP nsAddbookProtocolHandler::GetScheme(nsACString &aScheme)
+{
+ aScheme = "addbook";
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddbookProtocolHandler::GetDefaultPort(int32_t *aDefaultPort)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddbookProtocolHandler::GetProtocolFlags(uint32_t *aUritype)
+{
+ *aUritype = URI_STD | URI_LOADABLE_BY_ANYONE | URI_FORBIDS_COOKIE_ACCESS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddbookProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aOriginCharset, // ignored
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ nsresult rv;
+ nsCOMPtr <nsIAddbookUrl> addbookUrl = do_CreateInstance(NS_ADDBOOKURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = addbookUrl->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIURI> uri = do_QueryInterface(addbookUrl, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ADDREF(*_retval = uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAddbookProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+nsresult
+nsAddbookProtocolHandler::GenerateXMLOutputChannel( nsString &aOutput,
+ nsIAddbookUrl *addbookUrl,
+ nsIURI *aURI,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringInputStream> inStr(do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF16toUTF8 utf8String(aOutput.get());
+
+ rv = inStr->SetData(utf8String.get(), utf8String.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLoadInfo) {
+ return NS_NewInputStreamChannelInternal(_retval,
+ aURI,
+ inStr,
+ NS_LITERAL_CSTRING("text/xml"),
+ EmptyCString(),
+ aLoadInfo);
+ }
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipalfailed.");
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_NewInputStreamChannel(_retval, aURI, inStr,
+ nullPrincipal, nsILoadInfo::SEC_NORMAL,
+ nsIContentPolicy::TYPE_OTHER,
+ NS_LITERAL_CSTRING("text/xml"));
+}
+
+NS_IMETHODIMP
+nsAddbookProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+NS_IMETHODIMP
+nsAddbookProtocolHandler::NewChannel2(nsIURI *aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel **_retval)
+{
+ nsresult rv;
+ nsCOMPtr <nsIAddbookUrl> addbookUrl = do_QueryInterface(aURI, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = addbookUrl->GetAddbookOperation(&mAddbookOperation);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (mAddbookOperation == nsIAddbookUrlOperation::InvalidUrl) {
+ nsAutoString errorString;
+ errorString.AssignLiteral("Unsupported format/operation requested for ");
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ errorString.Append(NS_ConvertUTF8toUTF16(spec));
+ rv = GenerateXMLOutputChannel(errorString, addbookUrl, aURI, aLoadInfo, _retval);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+ }
+
+ if (mAddbookOperation == nsIAddbookUrlOperation::AddVCard) {
+ // create an empty pipe for use with the input stream channel.
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+
+ rv = pipe->Init(false, false, 0, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(pipeIn)));
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(pipeOut)));
+
+ pipeOut->Close();
+ if (aLoadInfo) {
+ return NS_NewInputStreamChannelInternal(_retval,
+ aURI,
+ pipeIn,
+ NS_LITERAL_CSTRING("application/x-addvcard"),
+ EmptyCString(),
+ aLoadInfo);
+ }
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed.");
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_NewInputStreamChannel(_retval, aURI, pipeIn,
+ nullPrincipal, nsILoadInfo::SEC_NORMAL, nsIContentPolicy::TYPE_OTHER,
+ NS_LITERAL_CSTRING("application/x-addvcard"));
+ }
+
+ nsString output;
+ rv = GeneratePrintOutput(addbookUrl, output);
+ if (NS_FAILED(rv)) {
+ output.AssignLiteral("failed to print. url=");
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+ output.Append(NS_ConvertUTF8toUTF16(spec));
+ }
+
+ rv = GenerateXMLOutputChannel(output, addbookUrl, aURI, aLoadInfo, _retval);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsresult
+nsAddbookProtocolHandler::GeneratePrintOutput(nsIAddbookUrl *addbookUrl,
+ nsString &aOutput)
+{
+ NS_ENSURE_ARG_POINTER(addbookUrl);
+
+ nsAutoCString uri;
+ nsresult rv = addbookUrl->GetPath(uri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ /* turn
+ "//moz-abmdbdirectory/abook.mab?action=print"
+ into "moz-abmdbdirectory://abook.mab"
+ */
+
+ /* step 1:
+ turn "//moz-abmdbdirectory/abook.mab?action=print"
+ into "moz-abmdbdirectory/abook.mab?action=print"
+ */
+ if (uri[0] != '/' && uri[1] != '/')
+ return NS_ERROR_UNEXPECTED;
+
+ uri.Cut(0,2);
+
+ /* step 2:
+ turn "moz-abmdbdirectory/abook.mab?action=print"
+ into "moz-abmdbdirectory/abook.mab"
+ */
+ int32_t pos = uri.Find("?action=print");
+ if (pos == -1)
+ return NS_ERROR_UNEXPECTED;
+
+ uri.SetLength(pos);
+
+ /* step 2:
+ turn "moz-abmdbdirectory/abook.mab"
+ into "moz-abmdbdirectory://abook.mab"
+ */
+ pos = uri.FindChar('/');
+ if (pos == -1)
+ return NS_ERROR_UNEXPECTED;
+
+ uri.Insert('/', pos);
+ uri.Insert(':', pos);
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = BuildDirectoryXML(directory, aOutput);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsAddbookProtocolHandler::BuildDirectoryXML(nsIAbDirectory *aDirectory,
+ nsString &aOutput)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ nsresult rv;
+ nsCOMPtr<nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr<nsIAbCard> card;
+
+ aOutput.AppendLiteral("<?xml version=\"1.0\"?>\n"
+ "<?xml-stylesheet type=\"text/css\" href=\"chrome://messagebody/content/addressbook/print.css\"?>\n"
+ "<directory>\n");
+
+ // Get Address Book string and set it as title of XML document
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ if (stringBundleService) {
+ rv = stringBundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsString addrBook;
+ rv = bundle->GetStringFromName(u"addressBook", getter_Copies(addrBook));
+ if (NS_SUCCEEDED(rv)) {
+ aOutput.AppendLiteral("<title xmlns=\"http://www.w3.org/1999/xhtml\">");
+ aOutput.Append(addrBook);
+ aOutput.AppendLiteral("</title>\n");
+ }
+ }
+ }
+
+ // create a view and init it with the generated name sort order. Then, iterate
+ // over the view, getting the card for each row, and printing them.
+ nsString sortColumn;
+ nsCOMPtr <nsIAbView> view = do_CreateInstance("@mozilla.org/addressbook/abview;1", &rv);
+
+ view->SetView(aDirectory, nullptr, NS_LITERAL_STRING("GeneratedName"),
+ NS_LITERAL_STRING("ascending"), sortColumn);
+
+ int32_t numRows;
+ nsCOMPtr <nsITreeView> treeView = do_QueryInterface(view, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ treeView->GetRowCount(&numRows);
+
+ for (int32_t row = 0; row < numRows; row++)
+ {
+
+ nsCOMPtr <nsIAbCard> card;
+ view->GetCardFromRow(row, getter_AddRefs(card));
+ nsCString xmlSubstr;
+
+ rv = card->TranslateTo(NS_LITERAL_CSTRING("xml"), xmlSubstr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ aOutput.AppendLiteral("<separator/>");
+ aOutput.Append(NS_ConvertUTF8toUTF16(xmlSubstr));
+ aOutput.AppendLiteral("<separator/>");
+ }
+
+ aOutput.AppendLiteral("</directory>\n");
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAddbookProtocolHandler.h b/mailnews/addrbook/src/nsAddbookProtocolHandler.h
new file mode 100644
index 000000000..1eb07a4ff
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddbookProtocolHandler.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAddbookProtocolHandler_h___
+#define nsAddbookProtocolHandler_h___
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsAddbookProtocolHandler.h"
+#include "nsIProtocolHandler.h"
+#include "nsIAddbookUrl.h"
+#include "nsIAddrDatabase.h"
+
+class nsAddbookProtocolHandler : public nsIProtocolHandler
+{
+public:
+ nsAddbookProtocolHandler();
+
+ NS_DECL_ISUPPORTS
+
+ //////////////////////////////////////////////////////////////////////////
+ // We support the nsIProtocolHandler interface.
+ //////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIPROTOCOLHANDLER
+
+private:
+ virtual ~nsAddbookProtocolHandler();
+ nsresult GenerateXMLOutputChannel(nsString &aOutput,
+ nsIAddbookUrl *addbookUrl,
+ nsIURI *aURI,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel **_retval);
+
+ nsresult GeneratePrintOutput(nsIAddbookUrl *addbookUrl,
+ nsString &aOutput);
+
+ nsresult BuildDirectoryXML(nsIAbDirectory *aDirectory,
+ nsString &aOutput);
+
+ int32_t mAddbookOperation;
+};
+
+#endif /* nsAddbookProtocolHandler_h___ */
diff --git a/mailnews/addrbook/src/nsAddbookUrl.cpp b/mailnews/addrbook/src/nsAddbookUrl.cpp
new file mode 100644
index 000000000..2084ca467
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddbookUrl.cpp
@@ -0,0 +1,282 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsAddbookUrl.h"
+#include "nsStringGlue.h"
+#include "nsAbBaseCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsAutoPtr.h"
+
+/////////////////////////////////////////////////////////////////////////////////////
+// addbook url definition
+/////////////////////////////////////////////////////////////////////////////////////
+nsAddbookUrl::nsAddbookUrl()
+{
+ m_baseURL = do_CreateInstance(NS_SIMPLEURI_CONTRACTID);
+
+ mOperationType = nsIAddbookUrlOperation::InvalidUrl;
+}
+
+nsAddbookUrl::~nsAddbookUrl()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAddbookUrl, nsIAddbookUrl, nsIURI)
+
+NS_IMETHODIMP
+nsAddbookUrl::SetSpec(const nsACString &aSpec)
+{
+ nsresult rv = m_baseURL->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+nsresult nsAddbookUrl::ParseUrl()
+{
+ nsAutoCString pathStr;
+
+ nsresult rv = m_baseURL->GetPath(pathStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (strstr(pathStr.get(), "?action=print"))
+ mOperationType = nsIAddbookUrlOperation::PrintAddressBook;
+ else if (strstr(pathStr.get(), "?action=add"))
+ mOperationType = nsIAddbookUrlOperation::AddVCard;
+ else
+ mOperationType = nsIAddbookUrlOperation::InvalidUrl;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIURI support
+////////////////////////////////////////////////////////////////////////////////////
+
+
+NS_IMETHODIMP nsAddbookUrl::GetSpec(nsACString &aSpec)
+{
+ return m_baseURL->GetSpec(aSpec);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetPrePath(nsACString &aPrePath)
+{
+ return m_baseURL->GetPrePath(aPrePath);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetScheme(nsACString &aScheme)
+{
+ return m_baseURL->GetScheme(aScheme);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetScheme(const nsACString &aScheme)
+{
+ return m_baseURL->SetScheme(aScheme);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetUserPass(nsACString &aUserPass)
+{
+ return m_baseURL->GetUserPass(aUserPass);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetUserPass(const nsACString &aUserPass)
+{
+ return m_baseURL->SetUserPass(aUserPass);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetUsername(nsACString &aUsername)
+{
+ return m_baseURL->GetUsername(aUsername);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetUsername(const nsACString &aUsername)
+{
+ return m_baseURL->SetUsername(aUsername);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetPassword(nsACString &aPassword)
+{
+ return m_baseURL->GetPassword(aPassword);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetPassword(const nsACString &aPassword)
+{
+ return m_baseURL->SetPassword(aPassword);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetHostPort(nsACString &aHostPort)
+{
+ return m_baseURL->GetHostPort(aHostPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetHostPort(const nsACString &aHostPort)
+{
+ return m_baseURL->SetHostPort(aHostPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetHostAndPort(const nsACString &aHostPort)
+{
+ return m_baseURL->SetHostAndPort(aHostPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetHost(nsACString &aHost)
+{
+ return m_baseURL->GetHost(aHost);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetHost(const nsACString &aHost)
+{
+ return m_baseURL->SetHost(aHost);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetPort(int32_t *aPort)
+{
+ return m_baseURL->GetPort(aPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetPort(int32_t aPort)
+{
+ return m_baseURL->SetPort(aPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetPath(nsACString &aPath)
+{
+ return m_baseURL->GetPath(aPath);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetPath(const nsACString &aPath)
+{
+ m_baseURL->SetPath(aPath);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetAsciiHost(nsACString &aHostA)
+{
+ return m_baseURL->GetAsciiHost(aHostA);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetAsciiHostPort(nsACString &aHostPortA)
+{
+ return m_baseURL->GetAsciiHostPort(aHostPortA);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetAsciiSpec(nsACString &aSpecA)
+{
+ return m_baseURL->GetAsciiSpec(aSpecA);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetOriginCharset(nsACString &aOriginCharset)
+{
+ return m_baseURL->GetOriginCharset(aOriginCharset);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SchemeIs(const char *aScheme, bool *_retval)
+{
+ return m_baseURL->SchemeIs(aScheme, _retval);
+}
+
+NS_IMETHODIMP nsAddbookUrl::Equals(nsIURI *other, bool *_retval)
+{
+ // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its
+ // Equals method. The other nsMailtoUrl will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other)
+ return other->Equals(m_baseURL, _retval);
+
+ return m_baseURL->Equals(other, _retval);
+}
+
+nsresult
+nsAddbookUrl::CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef, nsIURI** _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ RefPtr<nsAddbookUrl> clone = new nsAddbookUrl();
+
+ if (!clone)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+ if (aRefHandlingMode == eHonorRef) {
+ rv = m_baseURL->Clone(getter_AddRefs(clone->m_baseURL));
+ } else if (aRefHandlingMode == eReplaceRef) {
+ rv = m_baseURL->CloneWithNewRef(newRef, getter_AddRefs(clone->m_baseURL));
+ } else {
+ rv = m_baseURL->CloneIgnoringRef(getter_AddRefs(clone->m_baseURL));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ clone->ParseUrl();
+ clone.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddbookUrl::Clone(nsIURI **_retval)
+{
+ return CloneInternal(eHonorRef, EmptyCString(), _retval);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::CloneIgnoringRef(nsIURI** _retval)
+{
+ return CloneInternal(eIgnoreRef, EmptyCString(), _retval);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::CloneWithNewRef(const nsACString& newRef, nsIURI** _retval)
+{
+ return CloneInternal(eReplaceRef, newRef, _retval);
+}
+
+NS_IMETHODIMP nsAddbookUrl::Resolve(const nsACString &relativePath, nsACString &result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::GetRef(nsACString &result)
+{
+ return m_baseURL->GetRef(result);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::SetRef(const nsACString &aRef)
+{
+ m_baseURL->SetRef(aRef);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsAddbookUrl::EqualsExceptRef(nsIURI *other, bool *_retval)
+{
+ // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its
+ // Equals method. The other nsMailtoUrl will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other)
+ return other->EqualsExceptRef(m_baseURL, _retval);
+
+ return m_baseURL->EqualsExceptRef(other, _retval);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::GetSpecIgnoringRef(nsACString &result)
+{
+ return m_baseURL->GetSpecIgnoringRef(result);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::GetHasRef(bool *result)
+{
+ return m_baseURL->GetHasRef(result);
+}
+
+//
+// Specific nsAddbookUrl operations
+//
+NS_IMETHODIMP
+nsAddbookUrl::GetAddbookOperation(int32_t *_retval)
+{
+ *_retval = mOperationType;
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAddbookUrl.h b/mailnews/addrbook/src/nsAddbookUrl.h
new file mode 100644
index 000000000..71a34abe8
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddbookUrl.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAddbookUrl_h__
+#define nsAddbookUrl_h__
+
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsIAddbookUrl.h"
+
+class nsAddbookUrl : public nsIAddbookUrl
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIADDBOOKURL
+
+ nsAddbookUrl();
+
+protected:
+ enum RefHandlingEnum {
+ eIgnoreRef,
+ eHonorRef,
+ eReplaceRef
+ };
+ virtual ~nsAddbookUrl();
+ nsresult
+ CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef, nsIURI** _retval);
+
+ nsresult ParseUrl();
+ int32_t mOperationType; // the internal ID for the operation
+
+ nsCOMPtr<nsIURI> m_baseURL; // the base URL for the object
+};
+
+#endif // nsAddbookUrl_h__
diff --git a/mailnews/addrbook/src/nsAddrDatabase.cpp b/mailnews/addrbook/src/nsAddrDatabase.cpp
new file mode 100644
index 000000000..ea29ba8af
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddrDatabase.cpp
@@ -0,0 +1,3335 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// this file implements the nsAddrDatabase interface using the MDB Interface.
+
+#include "nsAddrDatabase.h"
+#include "nsStringGlue.h"
+#include "nsAutoPtr.h"
+#include "nsUnicharUtils.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsMorkCID.h"
+#include "nsIMdbFactoryFactory.h"
+#include "prprf.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIPromptService.h"
+#include "nsIStringBundle.h"
+#include "nsIFile.h"
+#include "nsEmbedCID.h"
+#include "nsIProperty.h"
+#include "nsIVariant.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIAbManager.h"
+#include "mozilla/Services.h"
+#include <algorithm>
+
+#define ID_PAB_TABLE 1
+#define ID_DELETEDCARDS_TABLE 2
+
+// There's two books by default, although Mac may have one more, so set this
+// to three. Its not going to affect much, but will save us a few reallocations
+// when the cache is allocated.
+const uint32_t kInitialAddrDBCacheSize = 3;
+
+static const char kPabTableKind[] = "ns:addrbk:db:table:kind:pab";
+static const char kDeletedCardsTableKind[] = "ns:addrbk:db:table:kind:deleted"; // this table is used to keep the deleted cards
+
+static const char kCardRowScope[] = "ns:addrbk:db:row:scope:card:all";
+static const char kListRowScope[] = "ns:addrbk:db:row:scope:list:all";
+static const char kDataRowScope[] = "ns:addrbk:db:row:scope:data:all";
+
+#define DATAROW_ROWID 1
+
+#define COLUMN_STR_MAX 16
+
+#define PURGE_CUTOFF_COUNT 50
+
+static const char kRecordKeyColumn[] = "RecordKey";
+static const char kLastRecordKeyColumn[] = "LastRecordKey";
+static const char kRowIDProperty[] = "DbRowID";
+
+static const char kLowerListNameColumn[] = "LowercaseListName";
+
+struct mdbOid gAddressBookTableOID;
+
+static const char kMailListAddressFormat[] = "Address%d";
+
+nsAddrDatabase::nsAddrDatabase()
+ : m_mdbEnv(nullptr), m_mdbStore(nullptr),
+ m_mdbPabTable(nullptr),
+ m_mdbDeletedCardsTable(nullptr),
+ m_mdbTokensInitialized(false),
+ m_PabTableKind(0),
+ m_MailListTableKind(0),
+ m_DeletedCardsTableKind(0),
+ m_CardRowScopeToken(0),
+ m_FirstNameColumnToken(0),
+ m_LastNameColumnToken(0),
+ m_PhoneticFirstNameColumnToken(0),
+ m_PhoneticLastNameColumnToken(0),
+ m_DisplayNameColumnToken(0),
+ m_NickNameColumnToken(0),
+ m_PriEmailColumnToken(0),
+ m_2ndEmailColumnToken(0),
+ m_WorkPhoneColumnToken(0),
+ m_HomePhoneColumnToken(0),
+ m_FaxColumnToken(0),
+ m_PagerColumnToken(0),
+ m_CellularColumnToken(0),
+ m_WorkPhoneTypeColumnToken(0),
+ m_HomePhoneTypeColumnToken(0),
+ m_FaxTypeColumnToken(0),
+ m_PagerTypeColumnToken(0),
+ m_CellularTypeColumnToken(0),
+ m_HomeAddressColumnToken(0),
+ m_HomeAddress2ColumnToken(0),
+ m_HomeCityColumnToken(0),
+ m_HomeStateColumnToken(0),
+ m_HomeZipCodeColumnToken(0),
+ m_HomeCountryColumnToken(0),
+ m_WorkAddressColumnToken(0),
+ m_WorkAddress2ColumnToken(0),
+ m_WorkCityColumnToken(0),
+ m_WorkStateColumnToken(0),
+ m_WorkZipCodeColumnToken(0),
+ m_WorkCountryColumnToken(0),
+ m_CompanyColumnToken(0),
+ m_AimScreenNameColumnToken(0),
+ m_AnniversaryYearColumnToken(0),
+ m_AnniversaryMonthColumnToken(0),
+ m_AnniversaryDayColumnToken(0),
+ m_SpouseNameColumnToken(0),
+ m_FamilyNameColumnToken(0),
+ m_DefaultAddressColumnToken(0),
+ m_CategoryColumnToken(0),
+ m_WebPage1ColumnToken(0),
+ m_WebPage2ColumnToken(0),
+ m_BirthYearColumnToken(0),
+ m_BirthMonthColumnToken(0),
+ m_BirthDayColumnToken(0),
+ m_Custom1ColumnToken(0),
+ m_Custom2ColumnToken(0),
+ m_Custom3ColumnToken(0),
+ m_Custom4ColumnToken(0),
+ m_NotesColumnToken(0),
+ m_LastModDateColumnToken(0),
+ m_MailFormatColumnToken(0),
+ m_PopularityIndexColumnToken(0),
+ m_AddressCharSetColumnToken(0),
+ m_LastRecordKey(0),
+ m_dbDirectory(nullptr)
+{
+}
+
+nsAddrDatabase::~nsAddrDatabase()
+{
+ Close(false); // better have already been closed.
+
+ // better not be any listeners, because we're going away.
+ NS_ASSERTION(m_ChangeListeners.Length() == 0, "shouldn't have any listeners");
+
+ RemoveFromCache(this);
+ // clean up after ourself!
+ if (m_mdbPabTable)
+ m_mdbPabTable->Release();
+ if (m_mdbDeletedCardsTable)
+ m_mdbDeletedCardsTable->Release();
+ NS_IF_RELEASE(m_mdbStore);
+ NS_IF_RELEASE(m_mdbEnv);
+}
+
+NS_IMPL_ISUPPORTS(nsAddrDatabase, nsIAddrDatabase, nsIAddrDBAnnouncer)
+
+NS_IMETHODIMP nsAddrDatabase::AddListener(nsIAddrDBListener *listener)
+{
+ NS_ENSURE_ARG_POINTER(listener);
+ return m_ChangeListeners.AppendElement(listener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDatabase::RemoveListener(nsIAddrDBListener *listener)
+{
+ NS_ENSURE_ARG_POINTER(listener);
+ return m_ChangeListeners.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDatabase::NotifyCardAttribChange(uint32_t abCode)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
+ OnCardAttribChange, (abCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::NotifyCardEntryChange(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent)
+{
+ int32_t currentDisplayNameVersion = 0;
+
+ //Update "mail.displayname.version" prefernce
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ prefs->GetIntPref("mail.displayname.version",&currentDisplayNameVersion);
+
+ prefs->SetIntPref("mail.displayname.version",++currentDisplayNameVersion);
+
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
+ OnCardEntryChange, (aAbCode, aCard, aParent));
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::NotifyListEntryChange(uint32_t abCode, nsIAbDirectory *dir)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
+ OnListEntryChange, (abCode, dir));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsAddrDatabase::NotifyAnnouncerGoingAway(void)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
+ OnAnnouncerGoingAway, ());
+ return NS_OK;
+}
+
+
+// Apparently its not good for nsTArray to be allocated as static. Don't know
+// why it isn't but its not, so don't think about making it a static variable.
+// Maybe bz knows.
+nsTArray<nsAddrDatabase*>* nsAddrDatabase::m_dbCache = nullptr;
+
+nsTArray<nsAddrDatabase*>*
+nsAddrDatabase::GetDBCache()
+{
+ if (!m_dbCache)
+ m_dbCache = new AutoTArray<nsAddrDatabase*, kInitialAddrDBCacheSize>;
+
+ return m_dbCache;
+}
+
+void
+nsAddrDatabase::CleanupCache()
+{
+ if (m_dbCache)
+ {
+ for (int32_t i = m_dbCache->Length() - 1; i >= 0; --i)
+ {
+ nsAddrDatabase* pAddrDB = m_dbCache->ElementAt(i);
+ if (pAddrDB)
+ pAddrDB->ForceClosed();
+ }
+ // NS_ASSERTION(m_dbCache.Length() == 0, "some msg dbs left open"); // better not be any open db's.
+ delete m_dbCache;
+ m_dbCache = nullptr;
+ }
+}
+
+//----------------------------------------------------------------------
+// FindInCache - this addrefs the db it finds.
+//----------------------------------------------------------------------
+nsAddrDatabase* nsAddrDatabase::FindInCache(nsIFile *dbName)
+{
+ nsTArray<nsAddrDatabase*>* dbCache = GetDBCache();
+ uint32_t length = dbCache->Length();
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ nsAddrDatabase* pAddrDB = dbCache->ElementAt(i);
+ if (pAddrDB->MatchDbName(dbName))
+ {
+ NS_ADDREF(pAddrDB);
+ return pAddrDB;
+ }
+ }
+ return nullptr;
+}
+
+bool nsAddrDatabase::MatchDbName(nsIFile* dbName) // returns true if they match
+{
+ bool dbMatches = false;
+
+ nsresult rv = m_dbName->Equals(dbName, &dbMatches);
+ if (NS_FAILED(rv))
+ return false;
+
+ return dbMatches;
+}
+
+//----------------------------------------------------------------------
+// RemoveFromCache
+//----------------------------------------------------------------------
+void nsAddrDatabase::RemoveFromCache(nsAddrDatabase* pAddrDB)
+{
+ if (m_dbCache)
+ m_dbCache->RemoveElement(pAddrDB);
+}
+
+void nsAddrDatabase::GetMDBFactory(nsIMdbFactory ** aMdbFactory)
+{
+ if (!mMdbFactory)
+ {
+ nsresult rv;
+ nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && mdbFactoryService)
+ rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory));
+ }
+ NS_IF_ADDREF(*aMdbFactory = mMdbFactory);
+}
+
+/* caller need to delete *aDbPath */
+NS_IMETHODIMP nsAddrDatabase::GetDbPath(nsIFile* *aDbPath)
+{
+ if (!aDbPath)
+ return NS_ERROR_NULL_POINTER;
+
+ return m_dbName->Clone(aDbPath);
+}
+
+NS_IMETHODIMP nsAddrDatabase::SetDbPath(nsIFile* aDbPath)
+{
+ return aDbPath->Clone(getter_AddRefs(m_dbName));
+}
+
+NS_IMETHODIMP nsAddrDatabase::Open
+(nsIFile *aMabFile, bool aCreate, bool upgrading /* unused */, nsIAddrDatabase** pAddrDB)
+{
+ *pAddrDB = nullptr;
+
+ nsAddrDatabase *pAddressBookDB = FindInCache(aMabFile);
+
+ if (pAddressBookDB) {
+ *pAddrDB = pAddressBookDB;
+ return NS_OK;
+ }
+
+ nsresult rv = OpenInternal(aMabFile, aCreate, pAddrDB);
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+
+ if (rv == NS_ERROR_FILE_ACCESS_DENIED)
+ {
+ static bool gAlreadyAlerted;
+ // only do this once per session to avoid annoying the user
+ if (!gAlreadyAlerted)
+ {
+ gAlreadyAlerted = true;
+ nsAutoString mabFileName;
+ rv = aMabFile->GetLeafName(mabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AlertAboutLockedMabFile(mabFileName.get());
+
+ // We just overwrote rv, so return the proper value here.
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ }
+ // try one more time
+ // but first rename corrupt mab file
+ // and prompt the user
+ else if (aCreate)
+ {
+ nsCOMPtr<nsIFile> dummyBackupMabFile;
+ nsCOMPtr<nsIFile> actualBackupMabFile;
+
+ // First create a clone of the corrupt mab file that we'll
+ // use to generate the name for the backup file that we are
+ // going to move it to.
+ rv = aMabFile->Clone(getter_AddRefs(dummyBackupMabFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now create a second clone that we'll use to do the move
+ // (this allows us to leave the original name intact)
+ rv = aMabFile->Clone(getter_AddRefs(actualBackupMabFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now we try and generate a new name for the corrupt mab
+ // file using the dummy backup mab file
+
+ // First append .bak - we have to do this the long way as
+ // AppendNative is to the path, not the LeafName.
+ nsAutoCString dummyBackupMabFileName;
+ rv = dummyBackupMabFile->GetNativeLeafName(dummyBackupMabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dummyBackupMabFileName.Append(NS_LITERAL_CSTRING(".bak"));
+
+ rv = dummyBackupMabFile->SetNativeLeafName(dummyBackupMabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now see if we can create it unique
+ rv = dummyBackupMabFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now get the new name
+ nsAutoCString backupMabFileName;
+ rv = dummyBackupMabFile->GetNativeLeafName(backupMabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // And the parent directory
+ nsCOMPtr<nsIFile> parentDir;
+ rv = dummyBackupMabFile->GetParent(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now move the corrupt file to its backup location
+ rv = actualBackupMabFile->MoveToNative(parentDir, backupMabFileName);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to rename corrupt mab file");
+
+ if (NS_SUCCEEDED(rv)) {
+ // now we can try to recreate the original mab file
+ rv = OpenInternal(aMabFile, aCreate, pAddrDB);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create .mab file, after rename");
+
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString originalMabFileName;
+ rv = aMabFile->GetLeafName(originalMabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if this fails, we don't care
+ (void)AlertAboutCorruptMabFile(originalMabFileName.get(),
+ NS_ConvertASCIItoUTF16(backupMabFileName).get());
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsAddrDatabase::DisplayAlert(const char16_t *titleName, const char16_t *alertStringName, const char16_t **formatStrings, int32_t numFormatStrings)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString alertMessage;
+ rv = bundle->FormatStringFromName(alertStringName, formatStrings, numFormatStrings,
+ getter_Copies(alertMessage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString alertTitle;
+ rv = bundle->GetStringFromName(titleName, getter_Copies(alertTitle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPromptService> prompter =
+ do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return prompter->Alert(nullptr /* we don't know the parent window */, alertTitle.get(), alertMessage.get());
+}
+
+nsresult nsAddrDatabase::AlertAboutCorruptMabFile(const char16_t *aOldFileName, const char16_t *aNewFileName)
+{
+ const char16_t *formatStrings[] = { aOldFileName, aOldFileName, aNewFileName };
+ return DisplayAlert(u"corruptMabFileTitle",
+ u"corruptMabFileAlert", formatStrings, 3);
+}
+
+nsresult nsAddrDatabase::AlertAboutLockedMabFile(const char16_t *aFileName)
+{
+ const char16_t *formatStrings[] = { aFileName };
+ return DisplayAlert(u"lockedMabFileTitle",
+ u"lockedMabFileAlert", formatStrings, 1);
+}
+
+nsresult
+nsAddrDatabase::OpenInternal(nsIFile *aMabFile, bool aCreate, nsIAddrDatabase** pAddrDB)
+{
+ nsAddrDatabase *pAddressBookDB = new nsAddrDatabase();
+ if (!pAddressBookDB) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ADDREF(pAddressBookDB);
+
+ nsresult rv = pAddressBookDB->OpenMDB(aMabFile, aCreate);
+ if (NS_SUCCEEDED(rv))
+ {
+ pAddressBookDB->SetDbPath(aMabFile);
+ GetDBCache()->AppendElement(pAddressBookDB);
+ *pAddrDB = pAddressBookDB;
+ }
+ else
+ {
+ *pAddrDB = nullptr;
+ pAddressBookDB->ForceClosed();
+ NS_IF_RELEASE(pAddressBookDB);
+ pAddressBookDB = nullptr;
+ }
+ return rv;
+}
+
+// Open the MDB database synchronously. If successful, this routine
+// will set up the m_mdbStore and m_mdbEnv of the database object
+// so other database calls can work.
+NS_IMETHODIMP nsAddrDatabase::OpenMDB(nsIFile *dbName, bool create)
+{
+ nsresult ret;
+ nsCOMPtr<nsIMdbFactory> mdbFactory;
+ GetMDBFactory(getter_AddRefs(mdbFactory));
+ NS_ENSURE_TRUE(mdbFactory, NS_ERROR_FAILURE);
+
+ ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv);
+ if (NS_SUCCEEDED(ret))
+ {
+ nsIMdbThumb *thumb = nullptr;
+ nsAutoCString filePath;
+
+ ret = dbName->GetNativePath(filePath);
+ NS_ENSURE_SUCCESS(ret, ret);
+
+ nsIMdbHeap* dbHeap = nullptr;
+
+ if (m_mdbEnv)
+ m_mdbEnv->SetAutoClear(true);
+
+ bool dbNameExists = false;
+ ret = dbName->Exists(&dbNameExists);
+ NS_ENSURE_SUCCESS(ret, ret);
+
+ if (!dbNameExists)
+ ret = NS_ERROR_FILE_NOT_FOUND;
+ else
+ {
+ mdbOpenPolicy inOpenPolicy;
+ mdb_bool canOpen;
+ mdbYarn outFormatVersion;
+ nsIMdbFile* oldFile = nullptr;
+ int64_t fileSize;
+ ret = dbName->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(ret, ret);
+
+ ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, filePath.get(),
+ mdbBool_kFalse, // not readonly, we want modifiable
+ &oldFile);
+ if ( oldFile )
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ ret = mdbFactory->CanOpenFilePort(m_mdbEnv, oldFile, // the file to investigate
+ &canOpen, &outFormatVersion);
+ if (NS_SUCCEEDED(ret) && canOpen)
+ {
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->OpenFileStore(m_mdbEnv, dbHeap,
+ oldFile, &inOpenPolicy, &thumb);
+ }
+ else if (fileSize != 0)
+ ret = NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ NS_RELEASE(oldFile); // always release our file ref, store has own
+ }
+ if (NS_FAILED(ret))
+ ret = NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
+ if (NS_SUCCEEDED(ret) && thumb)
+ {
+ mdb_count outTotal; // total somethings to do in operation
+ mdb_count outCurrent; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken; // is operation irreparably dead and broken?
+ do
+ {
+ ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
+ if (NS_FAILED(ret))
+ {
+ outDone = true;
+ break;
+ }
+ }
+ while (NS_SUCCEEDED(ret) && !outBroken && !outDone);
+ if (NS_SUCCEEDED(ret) && outDone)
+ {
+ ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore);
+ if (NS_SUCCEEDED(ret) && m_mdbStore)
+ {
+ ret = InitExistingDB();
+ create = false;
+ }
+ }
+ }
+ else if (create && ret != NS_ERROR_FILE_ACCESS_DENIED)
+ {
+ nsIMdbFile* newFile = 0;
+ ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, filePath.get(), &newFile);
+ if ( newFile )
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ mdbOpenPolicy inOpenPolicy;
+
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap,
+ newFile, &inOpenPolicy,
+ &m_mdbStore);
+ if (NS_SUCCEEDED(ret))
+ ret = InitNewDB();
+ }
+ NS_RELEASE(newFile); // always release our file ref, store has own
+ }
+ }
+ NS_IF_RELEASE(thumb);
+ }
+ return ret;
+}
+
+NS_IMETHODIMP nsAddrDatabase::CloseMDB(bool commit)
+{
+ if (commit)
+ Commit(nsAddrDBCommitType::kSessionCommit);
+//??? RemoveFromCache(this); // if we've closed it, better not leave it in the cache.
+ return NS_OK;
+}
+
+// force the database to close - this'll flush out anybody holding onto
+// a database without having a listener!
+// This is evil in the com world, but there are times we need to delete the file.
+NS_IMETHODIMP nsAddrDatabase::ForceClosed()
+{
+ nsresult err = NS_OK;
+ nsCOMPtr<nsIAddrDatabase> aDb(do_QueryInterface(this, &err));
+
+ // make sure someone has a reference so object won't get deleted out from under us.
+ AddRef();
+ NotifyAnnouncerGoingAway();
+ // OK, remove from cache first and close the store.
+ RemoveFromCache(this);
+
+ err = CloseMDB(false); // since we're about to delete it, no need to commit.
+ NS_IF_RELEASE(m_mdbStore);
+ Release();
+ return err;
+}
+
+NS_IMETHODIMP nsAddrDatabase::Commit(uint32_t commitType)
+{
+ nsresult err = NS_OK;
+ nsIMdbThumb *commitThumb = nullptr;
+
+ if (commitType == nsAddrDBCommitType::kLargeCommit ||
+ commitType == nsAddrDBCommitType::kSessionCommit)
+ {
+ mdb_percent outActualWaste = 0;
+ mdb_bool outShould;
+ if (m_mdbStore && m_mdbEnv)
+ {
+ // check how much space would be saved by doing a compress commit.
+ // If it's more than 30%, go for it.
+ // N.B. - I'm not sure this calls works in Mork for all cases.
+ err = m_mdbStore->ShouldCompress(m_mdbEnv, 30, &outActualWaste, &outShould);
+ if (NS_SUCCEEDED(err) && outShould)
+ {
+ commitType = nsAddrDBCommitType::kCompressCommit;
+ }
+ }
+ }
+
+ if (m_mdbStore && m_mdbEnv)
+ {
+ switch (commitType)
+ {
+ case nsAddrDBCommitType::kLargeCommit:
+ err = m_mdbStore->LargeCommit(m_mdbEnv, &commitThumb);
+ break;
+ case nsAddrDBCommitType::kSessionCommit:
+ // comment out until persistence works.
+ err = m_mdbStore->SessionCommit(m_mdbEnv, &commitThumb);
+ break;
+ case nsAddrDBCommitType::kCompressCommit:
+ err = m_mdbStore->CompressCommit(m_mdbEnv, &commitThumb);
+ break;
+ }
+ }
+ if (commitThumb && m_mdbEnv)
+ {
+ mdb_count outTotal = 0; // total somethings to do in operation
+ mdb_count outCurrent = 0; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken = false; // is operation irreparably dead and broken?
+ while (!outDone && !outBroken && NS_SUCCEEDED(err))
+ {
+ err = commitThumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
+ }
+ NS_RELEASE(commitThumb);
+ }
+ // ### do something with error, but clear it now because mork errors out on commits.
+ if (m_mdbEnv)
+ m_mdbEnv->ClearErrors();
+ return err;
+}
+
+NS_IMETHODIMP nsAddrDatabase::Close(bool forceCommit /* = TRUE */)
+{
+ return CloseMDB(forceCommit);
+}
+
+// set up empty tablesetc.
+nsresult nsAddrDatabase::InitNewDB()
+{
+ nsresult err = InitMDBInfo();
+ if (NS_SUCCEEDED(err))
+ {
+ err = InitPabTable();
+ err = InitLastRecorKey();
+ Commit(nsAddrDBCommitType::kLargeCommit);
+ }
+ return err;
+}
+
+nsresult nsAddrDatabase::AddRowToDeletedCardsTable(nsIAbCard *card, nsIMdbRow **pCardRow)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+ if (!m_mdbDeletedCardsTable)
+ rv = InitDeletedCardsTable(true);
+
+ if (NS_SUCCEEDED(rv)) {
+ // lets first purge old records if there are more than PURGE_CUTOFF_COUNT records
+ PurgeDeletedCardTable();
+ nsCOMPtr<nsIMdbRow> cardRow;
+ rv = GetNewRow(getter_AddRefs(cardRow));
+ if (NS_SUCCEEDED(rv) && cardRow) {
+ nsresult merror = m_mdbDeletedCardsTable->AddRow(m_mdbEnv, cardRow);
+ NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE);
+ nsString unicodeStr;
+ card->GetFirstName(unicodeStr);
+ AddFirstName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ card->GetLastName(unicodeStr);
+ AddLastName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ card->GetDisplayName(unicodeStr);
+ AddDisplayName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ card->GetPrimaryEmail(unicodeStr);
+ if (!unicodeStr.IsEmpty())
+ AddUnicodeToColumn(cardRow, m_PriEmailColumnToken, m_LowerPriEmailColumnToken, unicodeStr.get());
+
+ card->GetPropertyAsAString(k2ndEmailProperty, unicodeStr);
+ if (!unicodeStr.IsEmpty())
+ AddUnicodeToColumn(cardRow, m_2ndEmailColumnToken, m_Lower2ndEmailColumnToken, unicodeStr.get());
+
+ uint32_t nowInSeconds;
+ PRTime now = PR_Now();
+ PRTime2Seconds(now, &nowInSeconds);
+ AddIntColumn(cardRow, m_LastModDateColumnToken, nowInSeconds);
+
+ nsString value;
+ GetCardValue(card, CARD_ATTRIB_PALMID, getter_Copies(value));
+ if (!value.IsEmpty())
+ {
+ nsCOMPtr<nsIAbCard> addedCard;
+ rv = CreateCardFromDeletedCardsTable(cardRow, 0, getter_AddRefs(addedCard));
+ if (NS_SUCCEEDED(rv))
+ SetCardValue(addedCard, CARD_ATTRIB_PALMID, value.get(), false);
+ }
+ NS_IF_ADDREF(*pCardRow = cardRow);
+ }
+ Commit(nsAddrDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+nsresult nsAddrDatabase::DeleteRowFromDeletedCardsTable(nsIMdbRow *pCardRow)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult merror = NS_OK;
+ if (m_mdbDeletedCardsTable) {
+ pCardRow->CutAllColumns(m_mdbEnv);
+ merror = m_mdbDeletedCardsTable->CutRow(m_mdbEnv, pCardRow);
+ }
+ return merror;
+}
+
+
+nsresult nsAddrDatabase::InitDeletedCardsTable(bool aCreate)
+{
+ nsresult mdberr = NS_OK;
+ if (!m_mdbDeletedCardsTable)
+ {
+ struct mdbOid deletedCardsTableOID;
+ deletedCardsTableOID.mOid_Scope = m_CardRowScopeToken;
+ deletedCardsTableOID.mOid_Id = ID_DELETEDCARDS_TABLE;
+ if (m_mdbStore && m_mdbEnv)
+ {
+ m_mdbStore->GetTable(m_mdbEnv, &deletedCardsTableOID, &m_mdbDeletedCardsTable);
+ // if deletedCardsTable does not exist and bCreate is set, create a new one
+ if (!m_mdbDeletedCardsTable && aCreate)
+ {
+ mdberr = (nsresult) m_mdbStore->NewTableWithOid(m_mdbEnv, &deletedCardsTableOID,
+ m_DeletedCardsTableKind,
+ true, (const mdbOid*)nullptr,
+ &m_mdbDeletedCardsTable);
+ }
+ }
+ }
+ return mdberr;
+}
+
+nsresult nsAddrDatabase::InitPabTable()
+{
+ return m_mdbStore && m_mdbEnv ? m_mdbStore->NewTableWithOid(m_mdbEnv,
+ &gAddressBookTableOID,
+ m_PabTableKind,
+ false,
+ (const mdbOid*)nullptr,
+ &m_mdbPabTable)
+ : NS_ERROR_NULL_POINTER;
+}
+
+//save the last record number, store in m_DataRowScopeToken, row 1
+nsresult nsAddrDatabase::InitLastRecorKey()
+{
+ if (!m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsIMdbRow *pDataRow = nullptr;
+ mdbOid dataRowOid;
+ dataRowOid.mOid_Scope = m_DataRowScopeToken;
+ dataRowOid.mOid_Id = DATAROW_ROWID;
+ nsresult err = m_mdbStore->NewRowWithOid(m_mdbEnv, &dataRowOid, &pDataRow);
+
+ if (NS_SUCCEEDED(err) && pDataRow)
+ {
+ m_LastRecordKey = 0;
+ err = AddIntColumn(pDataRow, m_LastRecordKeyColumnToken, 0);
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pDataRow);
+ NS_RELEASE(pDataRow);
+ }
+ return err;
+}
+
+nsresult nsAddrDatabase::GetDataRow(nsIMdbRow **pDataRow)
+{
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsIMdbRow *pRow = nullptr;
+ mdbOid dataRowOid;
+ dataRowOid.mOid_Scope = m_DataRowScopeToken;
+ dataRowOid.mOid_Id = DATAROW_ROWID;
+ m_mdbStore->GetRow(m_mdbEnv, &dataRowOid, &pRow);
+ *pDataRow = pRow;
+
+ return pRow ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::GetLastRecordKey()
+{
+ if (!m_mdbPabTable)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr <nsIMdbRow> pDataRow;
+ nsresult err = GetDataRow(getter_AddRefs(pDataRow));
+
+ if (NS_SUCCEEDED(err) && pDataRow)
+ {
+ m_LastRecordKey = 0;
+ err = GetIntColumn(pDataRow, m_LastRecordKeyColumnToken, &m_LastRecordKey, 0);
+ if (NS_FAILED(err))
+ err = NS_ERROR_NOT_AVAILABLE;
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsAddrDatabase::UpdateLastRecordKey()
+{
+ if (!m_mdbPabTable || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr <nsIMdbRow> pDataRow;
+ nsresult err = GetDataRow(getter_AddRefs(pDataRow));
+
+ if (NS_SUCCEEDED(err) && pDataRow)
+ {
+ err = AddIntColumn(pDataRow, m_LastRecordKeyColumnToken, m_LastRecordKey);
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pDataRow);
+ return NS_OK;
+ }
+ else if (!pDataRow)
+ err = InitLastRecorKey();
+ else
+ return NS_ERROR_NOT_AVAILABLE;
+ return err;
+}
+
+nsresult nsAddrDatabase::InitExistingDB()
+{
+ nsresult err = InitMDBInfo();
+ if (NS_SUCCEEDED(err))
+ {
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->GetTable(m_mdbEnv, &gAddressBookTableOID, &m_mdbPabTable);
+ if (NS_SUCCEEDED(err) && m_mdbPabTable)
+ {
+ err = GetLastRecordKey();
+ if (err == NS_ERROR_NOT_AVAILABLE)
+ CheckAndUpdateRecordKey();
+ UpdateLowercaseEmailListName();
+ }
+ }
+ return err;
+}
+
+nsresult nsAddrDatabase::CheckAndUpdateRecordKey()
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ nsIMdbTableRowCursor* rowCursor = nullptr;
+ nsIMdbRow* findRow = nullptr;
+ mdb_pos rowPos = 0;
+
+ nsresult merror = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);
+
+ NS_ENSURE_TRUE(NS_SUCCEEDED(merror) && rowCursor, NS_ERROR_FAILURE);
+
+ nsCOMPtr <nsIMdbRow> pDataRow;
+ err = GetDataRow(getter_AddRefs(pDataRow));
+ if (NS_FAILED(err))
+ InitLastRecorKey();
+
+ do
+ { //add key to each card and mailing list row
+ merror = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos);
+ if (NS_SUCCEEDED(merror) && findRow)
+ {
+ mdbOid rowOid;
+
+ if (NS_SUCCEEDED(findRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ if (!IsDataRowScopeToken(rowOid.mOid_Scope))
+ {
+ m_LastRecordKey++;
+ err = AddIntColumn(findRow, m_RecordKeyColumnToken, m_LastRecordKey);
+ }
+ }
+ }
+ } while (findRow);
+
+ UpdateLastRecordKey();
+ Commit(nsAddrDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::UpdateLowercaseEmailListName()
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ nsIMdbTableRowCursor* rowCursor = nullptr;
+ nsIMdbRow* findRow = nullptr;
+ mdb_pos rowPos = 0;
+ bool commitRequired = false;
+
+ nsresult merror = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);
+
+ NS_ENSURE_TRUE(NS_SUCCEEDED(merror) && rowCursor, NS_ERROR_FAILURE);
+
+ do
+ { // Add lowercase primary+secondary email to each card and mailing list row.
+ merror = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos);
+ if (NS_SUCCEEDED(merror) && findRow)
+ {
+ mdbOid rowOid;
+
+ if (NS_SUCCEEDED(findRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ nsAutoString tempString;
+ if (IsCardRowScopeToken(rowOid.mOid_Scope))
+ {
+ err = GetStringColumn(findRow, m_LowerPriEmailColumnToken, tempString);
+ if (NS_FAILED(err)) // not set yet
+ {
+ err = ConvertAndAddLowercaseColumn(findRow, m_PriEmailColumnToken,
+ m_LowerPriEmailColumnToken);
+ commitRequired = commitRequired || NS_SUCCEEDED(err);
+ }
+
+ err = GetStringColumn(findRow, m_Lower2ndEmailColumnToken, tempString);
+ if (NS_FAILED(err)) // not set yet
+ {
+ err = ConvertAndAddLowercaseColumn(findRow, m_2ndEmailColumnToken,
+ m_Lower2ndEmailColumnToken);
+ commitRequired = commitRequired || NS_SUCCEEDED(err);
+ }
+ }
+ else if (IsListRowScopeToken(rowOid.mOid_Scope))
+ {
+ err = GetStringColumn(findRow, m_LowerListNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err)) // already set up
+ continue;
+
+ err = ConvertAndAddLowercaseColumn(findRow, m_ListNameColumnToken,
+ m_LowerListNameColumnToken);
+ commitRequired = commitRequired || NS_SUCCEEDED(err);
+ }
+ }
+ findRow->Release();
+ }
+ } while (findRow);
+
+ if (findRow)
+ findRow->Release();
+ rowCursor->Release();
+ if (commitRequired)
+ Commit(nsAddrDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+/*
+We store UTF8 strings in the database. We need to convert the UTF8
+string into unicode string, then convert to lower case. Before storing
+back into the database, we need to convert the lowercase unicode string
+into UTF8 string.
+*/
+nsresult nsAddrDatabase::ConvertAndAddLowercaseColumn
+(nsIMdbRow * row, mdb_token fromCol, mdb_token toCol)
+{
+ nsAutoString colString;
+
+ nsresult rv = GetStringColumn(row, fromCol, colString);
+ if (!colString.IsEmpty())
+ {
+ rv = AddLowercaseColumn(row, toCol, NS_ConvertUTF16toUTF8(colString).get());
+ }
+ return rv;
+}
+
+// Change the unicode string to lowercase, then convert to UTF8 string to store in db
+nsresult nsAddrDatabase::AddUnicodeToColumn(nsIMdbRow * row, mdb_token aColToken, mdb_token aLowerCaseColToken, const char16_t* aUnicodeStr)
+{
+ nsresult rv = AddCharStringColumn(row, aColToken, NS_ConvertUTF16toUTF8(aUnicodeStr).get());
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddLowercaseColumn(row, aLowerCaseColToken, NS_ConvertUTF16toUTF8(aUnicodeStr).get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+// initialize the various tokens and tables in our db's env
+nsresult nsAddrDatabase::InitMDBInfo()
+{
+ nsresult err = NS_OK;
+
+ if (!m_mdbTokensInitialized && m_mdbStore && m_mdbEnv)
+ {
+ m_mdbTokensInitialized = true;
+ err = m_mdbStore->StringToToken(m_mdbEnv, kCardRowScope, &m_CardRowScopeToken);
+ err = m_mdbStore->StringToToken(m_mdbEnv, kListRowScope, &m_ListRowScopeToken);
+ err = m_mdbStore->StringToToken(m_mdbEnv, kDataRowScope, &m_DataRowScopeToken);
+ gAddressBookTableOID.mOid_Scope = m_CardRowScopeToken;
+ gAddressBookTableOID.mOid_Id = ID_PAB_TABLE;
+ if (NS_SUCCEEDED(err))
+ {
+ m_mdbStore->StringToToken(m_mdbEnv, kFirstNameProperty, &m_FirstNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLastNameProperty, &m_LastNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPhoneticFirstNameProperty, &m_PhoneticFirstNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPhoneticLastNameProperty, &m_PhoneticLastNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kDisplayNameProperty, &m_DisplayNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kNicknameProperty, &m_NickNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPriEmailProperty, &m_PriEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLowerPriEmailColumn, &m_LowerPriEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, k2ndEmailProperty, &m_2ndEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLower2ndEmailColumn, &m_Lower2ndEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPreferMailFormatProperty, &m_MailFormatColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPopularityIndexProperty, &m_PopularityIndexColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneProperty, &m_WorkPhoneColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneProperty, &m_HomePhoneColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFaxProperty, &m_FaxColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPagerProperty, &m_PagerColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCellularProperty, &m_CellularColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneTypeProperty, &m_WorkPhoneTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneTypeProperty, &m_HomePhoneTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFaxTypeProperty, &m_FaxTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPagerTypeProperty, &m_PagerTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCellularTypeProperty, &m_CellularTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeAddressProperty, &m_HomeAddressColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeAddress2Property, &m_HomeAddress2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeCityProperty, &m_HomeCityColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeStateProperty, &m_HomeStateColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeZipCodeProperty, &m_HomeZipCodeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeCountryProperty, &m_HomeCountryColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkAddressProperty, &m_WorkAddressColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkAddress2Property, &m_WorkAddress2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkCityProperty, &m_WorkCityColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkStateProperty, &m_WorkStateColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkZipCodeProperty, &m_WorkZipCodeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkCountryProperty, &m_WorkCountryColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kJobTitleProperty, &m_JobTitleColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kDepartmentProperty, &m_DepartmentColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCompanyProperty, &m_CompanyColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kScreenNameProperty, &m_AimScreenNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryYearProperty, &m_AnniversaryYearColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryMonthProperty, &m_AnniversaryMonthColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryDayProperty, &m_AnniversaryDayColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kSpouseNameProperty, &m_SpouseNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFamilyNameProperty, &m_FamilyNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkWebPageProperty, &m_WebPage1ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeWebPageProperty, &m_WebPage2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kBirthYearProperty, &m_BirthYearColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kBirthMonthProperty, &m_BirthMonthColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kBirthDayProperty, &m_BirthDayColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom1Property, &m_Custom1ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom2Property, &m_Custom2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom3Property, &m_Custom3ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom4Property, &m_Custom4ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kNotesProperty, &m_NotesColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLastModifiedDateProperty, &m_LastModDateColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kRecordKeyColumn, &m_RecordKeyColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAddressCharSetColumn, &m_AddressCharSetColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLastRecordKeyColumn, &m_LastRecordKeyColumnToken);
+
+ err = m_mdbStore->StringToToken(m_mdbEnv, kPabTableKind, &m_PabTableKind);
+
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListName, &m_ListNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListNickName, &m_ListNickNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListDescription, &m_ListDescriptionColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListTotalAddresses, &m_ListTotalColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLowerListNameColumn, &m_LowerListNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kDeletedCardsTableKind, &m_DeletedCardsTableKind);
+ }
+ }
+ return err;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsAddrDatabase::AddRecordKeyColumnToRow(nsIMdbRow *pRow)
+{
+ if (pRow && m_mdbEnv)
+ {
+ m_LastRecordKey++;
+ nsresult err = AddIntColumn(pRow, m_RecordKeyColumnToken, m_LastRecordKey);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pRow);
+ UpdateLastRecordKey();
+ return err;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+nsresult nsAddrDatabase::AddAttributeColumnsToRow(nsIAbCard *card, nsIMdbRow *cardRow)
+{
+ nsresult rv = NS_OK;
+
+ if ((!card && !cardRow) || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid;
+ cardRow->GetOid(m_mdbEnv, &rowOid);
+
+ card->SetPropertyAsUint32(kRowIDProperty, rowOid.mOid_Id);
+
+ // add the row to the singleton table.
+ if (card && cardRow)
+ {
+ nsCOMPtr<nsISimpleEnumerator> properties;
+ rv = card->GetProperties(getter_AddRefs(properties));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(properties->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> next;
+ rv = properties->GetNext(getter_AddRefs(next));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIProperty> prop = do_QueryInterface(next);
+ nsAutoString name;
+ prop->GetName(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ prop->GetValue(getter_AddRefs(variant));
+
+ // We can't get as a char * because that messes up UTF8 stuff
+ nsAutoCString value;
+ variant->GetAsAUTF8String(value);
+
+ mdb_token token;
+ rv = m_mdbStore->StringToToken(m_mdbEnv, NS_ConvertUTF16toUTF8(name).get(), &token);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddCharStringColumn(cardRow, token, value.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Primary email is special: it is stored lowercase as well as in its
+ // original format.
+ nsAutoString primaryEmail;
+ card->GetPrimaryEmail(primaryEmail);
+ AddPrimaryEmail(cardRow, NS_ConvertUTF16toUTF8(primaryEmail).get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::CreateNewCardAndAddToDB(nsIAbCard *aNewCard, bool aNotify /* = FALSE */, nsIAbDirectory *aParent)
+{
+ nsCOMPtr <nsIMdbRow> cardRow;
+
+ if (!aNewCard || !m_mdbPabTable || !m_mdbEnv || !m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ // Per the UUID requirements, we want to try to reuse the local id if at all
+ // possible. nsACString::ToInteger probably won't fail if the local id looks
+ // like "23bozo" (returning 23 instead), but it's okay since we aren't going
+ // to overwrite anything with 23 if it already exists and the id for the row
+ // doesn't matter otherwise.
+ nsresult rv;
+
+ nsAutoCString id;
+ aNewCard->GetLocalId(id);
+
+ mdbOid rowId;
+ rowId.mOid_Scope = m_CardRowScopeToken;
+ rowId.mOid_Id = id.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ // Mork is being very naughty here. If the table does not have the oid, we
+ // should be able to reuse it. To be on the safe side, however, we're going
+ // to reference the store's reference count.
+ mdb_count rowCount = 1;
+ m_mdbStore->GetRowRefCount(m_mdbEnv, &rowId, &rowCount);
+ if (rowCount == 0)
+ {
+ // So apparently, the row can have a count of 0 yet still exist (probably
+ // meaning we haven't flushed it out of memory). In this case, we need to
+ // get the row and cut its cells.
+ rv = m_mdbStore->GetRow(m_mdbEnv, &rowId, getter_AddRefs(cardRow));
+ if (NS_SUCCEEDED(rv) && cardRow)
+ cardRow->CutAllColumns(m_mdbEnv);
+ else
+ rv = m_mdbStore->NewRowWithOid(m_mdbEnv, &rowId, getter_AddRefs(cardRow));
+ }
+ }
+
+ // If we don't have a cardRow yet, just get one with any ol' id.
+ if (!cardRow)
+ rv = GetNewRow(getter_AddRefs(cardRow));
+
+ if (NS_SUCCEEDED(rv) && cardRow)
+ {
+ AddAttributeColumnsToRow(aNewCard, cardRow);
+ AddRecordKeyColumnToRow(cardRow);
+
+ // we need to do this for dnd
+ uint32_t key = 0;
+ rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0);
+ if (NS_SUCCEEDED(rv))
+ aNewCard->SetPropertyAsUint32(kRecordKeyColumn, key);
+
+ aNewCard->GetPropertyAsAUTF8String(kRowIDProperty, id);
+ aNewCard->SetLocalId(id);
+
+ nsCOMPtr<nsIAbDirectory> abDir = do_QueryReferent(m_dbDirectory);
+ if (abDir)
+ abDir->GetUuid(id);
+
+ aNewCard->SetDirectoryId(id);
+
+ nsresult merror = m_mdbPabTable->AddRow(m_mdbEnv, cardRow);
+ NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE);
+ }
+ else
+ return rv;
+
+ // do notification
+ if (aNotify)
+ {
+ NotifyCardEntryChange(AB_NotifyInserted, aNewCard, aParent);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::CreateNewListCardAndAddToDB(nsIAbDirectory *aList, uint32_t listRowID, nsIAbCard *newCard, bool notify /* = FALSE */)
+{
+ if (!newCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsIMdbRow* pListRow = nullptr;
+ mdbOid listRowOid;
+ listRowOid.mOid_Scope = m_ListRowScopeToken;
+ listRowOid.mOid_Id = listRowID;
+ nsresult rv = m_mdbStore->GetRow(m_mdbEnv, &listRowOid, &pListRow);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!pListRow)
+ return NS_OK;
+
+ nsCOMPtr<nsIMutableArray> addressList;
+ rv = aList->GetAddressLists(getter_AddRefs(addressList));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t count;
+ addressList->GetLength(&count);
+
+ nsAutoString newEmail;
+ rv = newCard->GetPrimaryEmail(newEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t i;
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbCard> currentCard = do_QueryElementAt(addressList, i, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool equals;
+ rv = newCard->Equals(currentCard, &equals);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (equals) {
+ // card is already in list, bail out.
+ // this can happen when dropping a card on a mailing list from the directory that contains the mailing list
+ return NS_OK;
+ }
+
+ nsAutoString currentEmail;
+ rv = currentCard->GetPrimaryEmail(currentEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (newEmail.Equals(currentEmail)) {
+ // card is already in list, bail out
+ // this can happen when dropping a card on a mailing list from another directory (not the one that contains the mailing list
+ // or if you have multiple cards on a directory, with the same primary email address.
+ return NS_OK;
+ }
+ }
+
+ // start from 1
+ uint32_t totalAddress = GetListAddressTotal(pListRow) + 1;
+ SetListAddressTotal(pListRow, totalAddress);
+ nsCOMPtr<nsIAbCard> pNewCard;
+ rv = AddListCardColumnsToRow(newCard, pListRow, totalAddress, getter_AddRefs(pNewCard), true /* aInMailingList */, aList, nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ addressList->AppendElement(newCard, false);
+
+ if (notify)
+ NotifyCardEntryChange(AB_NotifyInserted, newCard, aList);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::AddListCardColumnsToRow
+(nsIAbCard *aPCard, nsIMdbRow *aPListRow, uint32_t aPos, nsIAbCard** aPNewCard, bool aInMailingList, nsIAbDirectory *aParent, nsIAbDirectory *aRoot)
+{
+ if (!aPCard || !aPListRow || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ nsString email;
+ aPCard->GetPrimaryEmail(email);
+ if (!email.IsEmpty())
+ {
+ nsIMdbRow *pCardRow = nullptr;
+ // Please DO NOT change the 3rd param of GetRowFromAttribute() call to
+ // true (ie, case insensitive) without reading bugs #128535 and #121478.
+ err = GetRowFromAttribute(kPriEmailProperty, NS_ConvertUTF16toUTF8(email),
+ false /* retain case */, &pCardRow, nullptr);
+ bool cardWasAdded = false;
+ if (NS_FAILED(err) || !pCardRow)
+ {
+ //New Email, then add a new row with this email
+ err = GetNewRow(&pCardRow);
+
+ if (NS_SUCCEEDED(err) && pCardRow)
+ {
+ AddPrimaryEmail(pCardRow, NS_ConvertUTF16toUTF8(email).get());
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pCardRow);
+ // Create a key for this row as well.
+ if (NS_SUCCEEDED(err))
+ AddRecordKeyColumnToRow(pCardRow);
+ }
+
+ cardWasAdded = true;
+ }
+
+ NS_ENSURE_TRUE(pCardRow, NS_ERROR_NULL_POINTER);
+
+ nsString name;
+ aPCard->GetDisplayName(name);
+ if (!name.IsEmpty()) {
+ AddDisplayName(pCardRow, NS_ConvertUTF16toUTF8(name).get());
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pCardRow);
+ }
+
+ nsCOMPtr<nsIAbCard> newCard;
+ CreateABCard(pCardRow, 0, getter_AddRefs(newCard));
+ NS_IF_ADDREF(*aPNewCard = newCard);
+
+ if (cardWasAdded) {
+ NotifyCardEntryChange(AB_NotifyInserted, newCard, aParent);
+ if (aRoot)
+ NotifyCardEntryChange(AB_NotifyInserted, newCard, aRoot);
+ }
+ else if (!aInMailingList) {
+ nsresult rv;
+ nsCOMPtr<nsIAddrDBListener> parentListener(do_QueryInterface(aParent, &rv));
+
+ // Ensure the parent is in the listener list (and hence wants to be notified)
+ if (NS_SUCCEEDED(rv) && m_ChangeListeners.Contains(parentListener))
+ parentListener->OnCardEntryChange(AB_NotifyInserted, aPCard, aParent);
+ }
+ else {
+ NotifyCardEntryChange(AB_NotifyPropertyChanged, aPCard, aParent);
+ }
+
+ //add a column with address row id to the list row
+ mdb_token listAddressColumnToken;
+
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, aPos);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ mdbOid outOid;
+
+ if (NS_SUCCEEDED(pCardRow->GetOid(m_mdbEnv, &outOid)))
+ {
+ //save address row ID to the list row
+ err = AddIntColumn(aPListRow, listAddressColumnToken, outOid.mOid_Id);
+ }
+ NS_RELEASE(pCardRow);
+
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::AddListAttributeColumnsToRow(nsIAbDirectory *list, nsIMdbRow *listRow, nsIAbDirectory *aParent)
+{
+ nsresult err = NS_OK;
+
+ if ((!list && !listRow) || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid, tableOid;
+ m_mdbPabTable->GetOid(m_mdbEnv, &tableOid);
+ listRow->GetOid(m_mdbEnv, &rowOid);
+
+ nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list,&err));
+ if (NS_SUCCEEDED(err))
+ dblist->SetDbRowID(rowOid.mOid_Id);
+
+ // add the row to the singleton table.
+ if (NS_SUCCEEDED(err) && listRow)
+ {
+ nsString unicodeStr;
+
+ list->GetDirName(unicodeStr);
+ if (!unicodeStr.IsEmpty())
+ AddUnicodeToColumn(listRow, m_ListNameColumnToken, m_LowerListNameColumnToken, unicodeStr.get());
+
+ list->GetListNickName(unicodeStr);
+ AddListNickName(listRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ list->GetDescription(unicodeStr);
+ AddListDescription(listRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ // XXX todo, this code has problems if you manually enter duplicate emails.
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ list->GetAddressLists(getter_AddRefs(pAddressLists));
+
+ uint32_t count;
+ pAddressLists->GetLength(&count);
+
+ nsAutoString email;
+ uint32_t i, total;
+ total = 0;
+ for (i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIAbCard> pCard(do_QueryElementAt(pAddressLists, i, &err));
+
+ if (NS_FAILED(err))
+ continue;
+
+ pCard->GetPrimaryEmail(email);
+ if (!email.IsEmpty())
+ total++;
+ }
+ SetListAddressTotal(listRow, total);
+
+ uint32_t pos;
+ for (i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIAbCard> pCard(do_QueryElementAt(pAddressLists, i, &err));
+
+ if (NS_FAILED(err))
+ continue;
+
+ bool listHasCard = false;
+ err = list->HasCard(pCard, &listHasCard);
+
+ // start from 1
+ pos = i + 1;
+ pCard->GetPrimaryEmail(email);
+ if (!email.IsEmpty())
+ {
+ nsCOMPtr<nsIAbCard> pNewCard;
+ err = AddListCardColumnsToRow(pCard, listRow, pos, getter_AddRefs(pNewCard), listHasCard, list, aParent);
+ if (pNewCard)
+ pAddressLists->ReplaceElementAt(pNewCard, i, false);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+uint32_t nsAddrDatabase::GetListAddressTotal(nsIMdbRow* listRow)
+{
+ uint32_t count = 0;
+ GetIntColumn(listRow, m_ListTotalColumnToken, &count, 0);
+ return count;
+}
+
+NS_IMETHODIMP nsAddrDatabase::SetListAddressTotal(nsIMdbRow* aListRow, uint32_t aTotal)
+{
+ return AddIntColumn(aListRow, m_ListTotalColumnToken, aTotal);
+}
+
+NS_IMETHODIMP nsAddrDatabase::FindRowByCard(nsIAbCard * aCard,nsIMdbRow **aRow)
+{
+ nsString primaryEmail;
+ aCard->GetPrimaryEmail(primaryEmail);
+ return GetRowForCharColumn(primaryEmail.get(), m_PriEmailColumnToken,
+ true, true, aRow, nullptr);
+}
+
+nsresult nsAddrDatabase::GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos, nsIMdbRow** cardRow)
+{
+ if (!m_mdbStore || !listRow || !cardRow || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdb_token listAddressColumnToken;
+
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ nsAutoString tempString;
+ mdb_id rowID;
+ nsresult err = GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0);
+ NS_ENSURE_SUCCESS(err, err);
+
+ return GetCardRowByRowID(rowID, cardRow);
+}
+
+NS_IMETHODIMP nsAddrDatabase::CreateMailListAndAddToDB(nsIAbDirectory *aNewList, bool aNotify /* = FALSE */, nsIAbDirectory *aParent)
+{
+ if (!aNewList || !m_mdbPabTable || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsIMdbRow *listRow;
+ nsresult err = GetNewListRow(&listRow);
+
+ if (NS_SUCCEEDED(err) && listRow)
+ {
+ AddListAttributeColumnsToRow(aNewList, listRow, aParent);
+ AddRecordKeyColumnToRow(listRow);
+ nsresult merror = m_mdbPabTable->AddRow(m_mdbEnv, listRow);
+ NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIAbCard> listCard;
+ CreateABListCard(listRow, getter_AddRefs(listCard));
+ NotifyCardEntryChange(AB_NotifyInserted, listCard, aParent);
+
+ NS_RELEASE(listRow);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+void nsAddrDatabase::DeleteCardFromAllMailLists(mdb_id cardRowID)
+{
+ if (!m_mdbEnv)
+ return;
+
+ nsCOMPtr <nsIMdbTableRowCursor> rowCursor;
+ m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor));
+
+ if (rowCursor)
+ {
+ nsCOMPtr <nsIMdbRow> pListRow;
+ mdb_pos rowPos;
+ do
+ {
+ nsresult err = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(pListRow), &rowPos);
+
+ if (NS_SUCCEEDED(err) && pListRow)
+ {
+ mdbOid rowOid;
+
+ if (NS_SUCCEEDED(pListRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ if (IsListRowScopeToken(rowOid.mOid_Scope))
+ DeleteCardFromListRow(pListRow, cardRowID);
+ }
+ }
+ } while (pListRow);
+ }
+}
+
+NS_IMETHODIMP nsAddrDatabase::DeleteCard(nsIAbCard *aCard, bool aNotify, nsIAbDirectory *aParent)
+{
+ if (!aCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ bool bIsMailList = false;
+ aCard->GetIsMailList(&bIsMailList);
+
+ // get the right row
+ nsIMdbRow* pCardRow = nullptr;
+ mdbOid rowOid;
+
+ rowOid.mOid_Scope = bIsMailList ? m_ListRowScopeToken : m_CardRowScopeToken;
+
+ err = aCard->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, &pCardRow);
+ NS_ENSURE_SUCCESS(err,err);
+ if (!pCardRow)
+ return NS_OK;
+
+ // Reset the directory id
+ aCard->SetDirectoryId(EmptyCString());
+
+ // Add the deleted card to the deletedcards table
+ nsCOMPtr <nsIMdbRow> cardRow;
+ AddRowToDeletedCardsTable(aCard, getter_AddRefs(cardRow));
+ err = DeleteRow(m_mdbPabTable, pCardRow);
+
+ //delete the person card from all mailing list
+ if (!bIsMailList)
+ DeleteCardFromAllMailLists(rowOid.mOid_Id);
+
+ if (NS_SUCCEEDED(err)) {
+ if (aNotify)
+ NotifyCardEntryChange(AB_NotifyDeleted, aCard, aParent);
+ }
+ else
+ DeleteRowFromDeletedCardsTable(cardRow);
+
+ NS_RELEASE(pCardRow);
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::DeleteCardFromListRow(nsIMdbRow* pListRow, mdb_id cardRowID)
+{
+ NS_ENSURE_ARG_POINTER(pListRow);
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ uint32_t totalAddress = GetListAddressTotal(pListRow);
+
+ uint32_t pos;
+ for (pos = 1; pos <= totalAddress; pos++)
+ {
+ mdb_token listAddressColumnToken;
+ mdb_id rowID;
+
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ err = GetIntColumn(pListRow, listAddressColumnToken, (uint32_t*)&rowID, 0);
+
+ if (cardRowID == rowID)
+ {
+ if (pos == totalAddress)
+ err = pListRow->CutColumn(m_mdbEnv, listAddressColumnToken);
+ else
+ {
+ //replace the deleted one with the last one and delete the last one
+ mdb_id lastRowID;
+ mdb_token lastAddressColumnToken;
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, totalAddress);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &lastAddressColumnToken);
+
+ err = GetIntColumn(pListRow, lastAddressColumnToken, (uint32_t*)&lastRowID, 0);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = AddIntColumn(pListRow, listAddressColumnToken, lastRowID);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = pListRow->CutColumn(m_mdbEnv, lastAddressColumnToken);
+ NS_ENSURE_SUCCESS(err, err);
+ }
+
+ // Reset total count after the card has been deleted.
+ SetListAddressTotal(pListRow, totalAddress-1);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::DeleteCardFromMailList(nsIAbDirectory *mailList, nsIAbCard *card, bool aNotify)
+{
+ if (!card || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ // get the right row
+ nsIMdbRow* pListRow = nullptr;
+ mdbOid listRowOid;
+ listRowOid.mOid_Scope = m_ListRowScopeToken;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList,&err));
+ NS_ENSURE_SUCCESS(err, err);
+
+ dbmailList->GetDbRowID((uint32_t*)&listRowOid.mOid_Id);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &listRowOid, &pListRow);
+ NS_ENSURE_SUCCESS(err,err);
+ if (!pListRow)
+ return NS_OK;
+
+ uint32_t cardRowID;
+
+ err = card->GetPropertyAsUint32(kRowIDProperty, &cardRowID);
+ if (NS_FAILED(err))
+ return NS_ERROR_NULL_POINTER;
+
+ err = DeleteCardFromListRow(pListRow, cardRowID);
+ if (NS_SUCCEEDED(err) && aNotify) {
+ NotifyCardEntryChange(AB_NotifyDeleted, card, mailList);
+ }
+ NS_RELEASE(pListRow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::SetCardValue(nsIAbCard *card, const char *name, const char16_t *value, bool notify)
+{
+ NS_ENSURE_ARG_POINTER(card);
+ NS_ENSURE_ARG_POINTER(name);
+ NS_ENSURE_ARG_POINTER(value);
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsIMdbRow> cardRow;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+
+ // it might be that the caller always has a nsAbMDBCard
+ rv = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!cardRow)
+ return NS_OK;
+
+ mdb_token token;
+ rv = m_mdbStore->StringToToken(m_mdbEnv, name, &token);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return AddCharStringColumn(cardRow, token, NS_ConvertUTF16toUTF8(value).get());
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetCardValue(nsIAbCard *card, const char *name, char16_t **value)
+{
+ if (!m_mdbStore || !card || !name || !value || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsIMdbRow> cardRow;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+
+ // it might be that the caller always has a nsAbMDBCard
+ rv = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!cardRow) {
+ *value = nullptr;
+ // this can happen when adding cards when editing a mailing list
+ return NS_OK;
+ }
+
+ mdb_token token;
+ m_mdbStore->StringToToken(m_mdbEnv, name, &token);
+
+ // XXX fix me
+ // avoid extra copying and allocations (did dmb already do this on the trunk?)
+ nsAutoString tempString;
+ rv = GetStringColumn(cardRow, token, tempString);
+ if (NS_FAILED(rv)) {
+ // not all cards are going this column
+ *value = nullptr;
+ return NS_OK;
+ }
+
+ *value = NS_strdup(tempString.get());
+ if (!*value)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetDeletedCardList(nsIArray **aResult)
+{
+ if (!m_mdbEnv || !aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ *aResult = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> resultCardArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make sure the member is set properly
+ InitDeletedCardsTable(false);
+ if (m_mdbDeletedCardsTable)
+ {
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ mdb_pos rowPos;
+ bool done = false;
+ nsCOMPtr<nsIMdbRow> currentRow;
+
+ m_mdbDeletedCardsTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor));
+ if (!rowCursor)
+ return NS_ERROR_FAILURE;
+ while (!done)
+ {
+ rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
+ if (currentRow && NS_SUCCEEDED(rv))
+ {
+ mdbOid rowOid;
+ if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ nsCOMPtr<nsIAbCard> card;
+ rv = CreateCardFromDeletedCardsTable(currentRow, 0, getter_AddRefs(card));
+ if (NS_SUCCEEDED(rv)) {
+ resultCardArray->AppendElement(card, false);
+ }
+ }
+ }
+ else
+ done = true;
+ }
+ }
+
+ NS_IF_ADDREF(*aResult = resultCardArray);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetDeletedCardCount(uint32_t *aCount)
+{
+ // initialize count first
+ *aCount = 0;
+ InitDeletedCardsTable(false);
+ if (m_mdbDeletedCardsTable)
+ return m_mdbDeletedCardsTable->GetCount(m_mdbEnv, aCount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::PurgeDeletedCardTable()
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ if (m_mdbDeletedCardsTable) {
+ mdb_count cardCount=0;
+ // if not too many cards let it be
+ m_mdbDeletedCardsTable->GetCount(m_mdbEnv, &cardCount);
+ if(cardCount < PURGE_CUTOFF_COUNT)
+ return NS_OK;
+ uint32_t purgeTimeInSec;
+ PRTime2Seconds(PR_Now(), &purgeTimeInSec);
+ purgeTimeInSec -= (182*24*60*60); // six months in seconds
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ nsresult rv = m_mdbDeletedCardsTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor));
+ while(NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMdbRow> currentRow;
+ mdb_pos rowPos;
+ rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
+ if(currentRow) {
+ uint32_t deletedTimeStamp = 0;
+ GetIntColumn(currentRow, m_LastModDateColumnToken, &deletedTimeStamp, 0);
+ // if record was deleted more than six months earlier, purge it
+ if(deletedTimeStamp && (deletedTimeStamp < purgeTimeInSec)) {
+ if(NS_SUCCEEDED(currentRow->CutAllColumns(m_mdbEnv)))
+ m_mdbDeletedCardsTable->CutRow(m_mdbEnv, currentRow);
+ }
+ else
+ // since the ordering in Mork is maintained and thus
+ // the cards added later appear on the top when retrieved
+ break;
+ }
+ else
+ break; // no more row
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::EditCard(nsIAbCard *aCard, bool aNotify, nsIAbDirectory *aParent)
+{
+ // XXX make sure this isn't getting called when we're just editing one or two well known fields
+ if (!aCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ nsCOMPtr <nsIMdbRow> cardRow;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+
+ uint32_t nowInSeconds;
+ PRTime now = PR_Now();
+ PRTime2Seconds(now, &nowInSeconds);
+ aCard->SetPropertyAsUint32(kLastModifiedDateProperty, nowInSeconds);
+ err = aCard->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow));
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (!cardRow)
+ return NS_OK;
+
+ err = AddAttributeColumnsToRow(aCard, cardRow);
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (aNotify)
+ NotifyCardEntryChange(AB_NotifyPropertyChanged, aCard, aParent);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::ContainsCard(nsIAbCard *card, bool *hasCard)
+{
+ if (!card || !m_mdbPabTable || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ mdb_bool hasOid;
+ mdbOid rowOid;
+ bool bIsMailList;
+
+ card->GetIsMailList(&bIsMailList);
+
+ if (bIsMailList)
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+ else
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+
+ err = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = m_mdbPabTable->HasOid(m_mdbEnv, &rowOid, &hasOid);
+ if (NS_SUCCEEDED(err))
+ {
+ *hasCard = hasOid;
+ }
+
+ return err;
+}
+
+NS_IMETHODIMP nsAddrDatabase::DeleteMailList(nsIAbDirectory *aMailList,
+ nsIAbDirectory *aParent)
+{
+ if (!aMailList || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ // get the row
+ nsCOMPtr<nsIMdbRow> pListRow;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(aMailList, &err));
+ NS_ENSURE_SUCCESS(err, err);
+ dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(pListRow));
+ NS_ENSURE_SUCCESS(err,err);
+
+ if (!pListRow)
+ return NS_OK;
+
+ nsCOMPtr<nsIAbCard> card;
+ err = CreateABListCard(pListRow, getter_AddRefs(card));
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = DeleteRow(m_mdbPabTable, pListRow);
+
+ if (NS_SUCCEEDED(err) && aParent)
+ NotifyCardEntryChange(AB_NotifyDeleted, card, aParent);
+
+ return err;
+}
+
+NS_IMETHODIMP nsAddrDatabase::EditMailList(nsIAbDirectory *mailList, nsIAbCard *listCard, bool notify)
+{
+ if (!mailList || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ nsIMdbRow* pListRow = nullptr;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList, &err));
+ NS_ENSURE_SUCCESS(err, err);
+ dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, &pListRow);
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (!pListRow)
+ return NS_OK;
+
+ err = AddListAttributeColumnsToRow(mailList, pListRow, mailList);
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (notify)
+ {
+ NotifyListEntryChange(AB_NotifyPropertyChanged, mailList);
+
+ if (listCard)
+ {
+ NotifyCardEntryChange(AB_NotifyPropertyChanged, listCard, mailList);
+ }
+ }
+
+ NS_RELEASE(pListRow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::ContainsMailList(nsIAbDirectory *mailList, bool *hasList)
+{
+ if (!mailList || !m_mdbPabTable || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ mdb_bool hasOid;
+ mdbOid rowOid;
+
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList,&err));
+ NS_ENSURE_SUCCESS(err, err);
+ dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id);
+
+ err = m_mdbPabTable->HasOid(m_mdbEnv, &rowOid, &hasOid);
+ if (NS_SUCCEEDED(err))
+ *hasList = hasOid;
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetNewRow(nsIMdbRow * *newRow)
+{
+ if (!m_mdbStore || !newRow || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ return m_mdbStore->NewRow(m_mdbEnv, m_CardRowScopeToken, newRow);
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetNewListRow(nsIMdbRow * *newRow)
+{
+ if (!m_mdbStore || !newRow || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ return m_mdbStore->NewRow(m_mdbEnv, m_ListRowScopeToken, newRow);
+}
+
+NS_IMETHODIMP nsAddrDatabase::AddCardRowToDB(nsIMdbRow *newRow)
+{
+ if (m_mdbPabTable && m_mdbEnv)
+ {
+ if (NS_SUCCEEDED(m_mdbPabTable->AddRow(m_mdbEnv, newRow)))
+ {
+ AddRecordKeyColumnToRow(newRow);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDatabase::AddLdifListMember(nsIMdbRow* listRow, const char* value)
+{
+ if (!m_mdbStore || !listRow || !value || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ uint32_t total = GetListAddressTotal(listRow);
+ //add member
+ nsAutoCString valueString(value);
+ nsAutoCString email;
+ int32_t emailPos = valueString.Find("mail=");
+ emailPos += strlen("mail=");
+ email = Substring(valueString, emailPos);
+ nsCOMPtr <nsIMdbRow> cardRow;
+ // Please DO NOT change the 3rd param of GetRowFromAttribute() call to
+ // true (ie, case insensitive) without reading bugs #128535 and #121478.
+ nsresult rv = GetRowFromAttribute(kPriEmailProperty, email, false /* retain case */,
+ getter_AddRefs(cardRow), nullptr);
+ if (NS_SUCCEEDED(rv) && cardRow)
+ {
+ mdbOid outOid;
+ mdb_id rowID = 0;
+ if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ // start from 1
+ total += 1;
+ mdb_token listAddressColumnToken;
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, total);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ rv = AddIntColumn(listRow, listAddressColumnToken, rowID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetListAddressTotal(listRow, total);
+ }
+ return NS_OK;
+}
+
+
+void nsAddrDatabase::GetCharStringYarn(char* str, struct mdbYarn* strYarn)
+{
+ strYarn->mYarn_Grow = nullptr;
+ strYarn->mYarn_Buf = str;
+ strYarn->mYarn_Size = PL_strlen((const char *) strYarn->mYarn_Buf) + 1;
+ strYarn->mYarn_Fill = strYarn->mYarn_Size - 1;
+ strYarn->mYarn_Form = 0;
+}
+
+void nsAddrDatabase::GetStringYarn(const nsAString & aStr, struct mdbYarn* strYarn)
+{
+ strYarn->mYarn_Buf = ToNewUTF8String(aStr);
+ strYarn->mYarn_Size = PL_strlen((const char *) strYarn->mYarn_Buf) + 1;
+ strYarn->mYarn_Fill = strYarn->mYarn_Size - 1;
+ strYarn->mYarn_Form = 0;
+}
+
+void nsAddrDatabase::GetIntYarn(uint32_t nValue, struct mdbYarn* intYarn)
+{
+ intYarn->mYarn_Fill = intYarn->mYarn_Size;
+ intYarn->mYarn_Form = 0;
+ intYarn->mYarn_Grow = nullptr;
+
+ PR_snprintf((char*)intYarn->mYarn_Buf, intYarn->mYarn_Size, "%lx", nValue);
+ intYarn->mYarn_Fill = PL_strlen((const char *) intYarn->mYarn_Buf);
+}
+
+nsresult nsAddrDatabase::AddCharStringColumn(nsIMdbRow* cardRow, mdb_column inColumn, const char* str)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+
+ GetCharStringYarn((char *) str, &yarn);
+ nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::AddStringColumn(nsIMdbRow* aCardRow, mdb_column aInColumn, const nsAString & aStr)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+
+ GetStringYarn(aStr, &yarn);
+ nsresult err = aCardRow->AddColumn(m_mdbEnv, aInColumn, &yarn);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::AddIntColumn(nsIMdbRow* cardRow, mdb_column inColumn, uint32_t nValue)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+ char yarnBuf[100];
+
+ yarn.mYarn_Buf = (void *) yarnBuf;
+ yarn.mYarn_Size = sizeof(yarnBuf);
+ GetIntYarn(nValue, &yarn);
+ nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::AddBoolColumn(nsIMdbRow* cardRow, mdb_column inColumn, bool bValue)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+ char yarnBuf[100];
+
+ yarn.mYarn_Buf = (void *) yarnBuf;
+ yarn.mYarn_Size = sizeof(yarnBuf);
+
+ GetIntYarn(bValue ? 1 : 0, &yarn);
+
+ nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::GetStringColumn(nsIMdbRow *cardRow, mdb_token outToken, nsString& str)
+{
+ nsresult err = NS_ERROR_NULL_POINTER;
+ nsIMdbCell *cardCell;
+
+ if (cardRow && m_mdbEnv)
+ {
+ err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
+ if (NS_SUCCEEDED(err) && cardCell)
+ {
+ struct mdbYarn yarn;
+ cardCell->AliasYarn(m_mdbEnv, &yarn);
+ NS_ConvertUTF8toUTF16 uniStr((const char*) yarn.mYarn_Buf, yarn.mYarn_Fill);
+ if (!uniStr.IsEmpty())
+ str.Assign(uniStr);
+ else
+ err = NS_ERROR_FAILURE;
+ cardCell->Release(); // always release ref
+ }
+ else
+ err = NS_ERROR_FAILURE;
+ }
+ return err;
+}
+
+void nsAddrDatabase::YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult)
+{
+ uint8_t numChars = std::min<mdb_fill>(8, yarn->mYarn_Fill);
+ *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars);
+}
+
+nsresult nsAddrDatabase::GetIntColumn
+(nsIMdbRow *cardRow, mdb_token outToken, uint32_t* pValue, uint32_t defaultValue)
+{
+ nsresult err = NS_ERROR_NULL_POINTER;
+ nsIMdbCell *cardCell;
+
+ if (pValue)
+ *pValue = defaultValue;
+ if (cardRow && m_mdbEnv)
+ {
+ err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
+ if (NS_SUCCEEDED(err) && cardCell)
+ {
+ struct mdbYarn yarn;
+ cardCell->AliasYarn(m_mdbEnv, &yarn);
+ YarnToUInt32(&yarn, pValue);
+ cardCell->Release();
+ }
+ else
+ err = NS_ERROR_FAILURE;
+ }
+ return err;
+}
+
+nsresult nsAddrDatabase::GetBoolColumn(nsIMdbRow *cardRow, mdb_token outToken, bool* pValue)
+{
+ NS_ENSURE_ARG_POINTER(pValue);
+
+ nsresult err = NS_ERROR_NULL_POINTER;
+ nsIMdbCell *cardCell;
+ uint32_t nValue = 0;
+
+ if (cardRow && m_mdbEnv)
+ {
+ err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
+ if (NS_SUCCEEDED(err) && cardCell)
+ {
+ struct mdbYarn yarn;
+ cardCell->AliasYarn(m_mdbEnv, &yarn);
+ YarnToUInt32(&yarn, &nValue);
+ cardCell->Release();
+ }
+ else
+ err = NS_ERROR_FAILURE;
+ }
+
+ *pValue = nValue ? true : false;
+ return err;
+}
+
+/* value is UTF8 string */
+NS_IMETHODIMP nsAddrDatabase::AddPrimaryEmail(nsIMdbRow *aRow, const char *aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+
+ nsresult rv = AddCharStringColumn(aRow, m_PriEmailColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddLowercaseColumn(aRow, m_LowerPriEmailColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+/* value is UTF8 string */
+NS_IMETHODIMP nsAddrDatabase::Add2ndEmail(nsIMdbRow *aRow, const char *aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+
+ nsresult rv = AddCharStringColumn(aRow, m_2ndEmailColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddLowercaseColumn(aRow, m_Lower2ndEmailColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+/* value is UTF8 string */
+NS_IMETHODIMP nsAddrDatabase::AddListName(nsIMdbRow *aRow, const char *aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+
+ nsresult rv = AddCharStringColumn(aRow, m_ListNameColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddLowercaseColumn(aRow, m_LowerListNameColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+/*
+columnValue is UTF8 string, need to convert back to lowercase unicode then
+back to UTF8 string
+*/
+nsresult nsAddrDatabase::AddLowercaseColumn
+(nsIMdbRow * row, mdb_token columnToken, const char* columnValue)
+{
+ nsresult rv = NS_OK;
+ if (columnValue)
+ {
+ NS_ConvertUTF8toUTF16 newUnicodeString(columnValue);
+ ToLowerCase(newUnicodeString);
+ rv = AddCharStringColumn(row, columnToken, NS_ConvertUTF16toUTF8(newUnicodeString).get());
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::InitCardFromRow(nsIAbCard *newCard, nsIMdbRow* cardRow)
+{
+ nsresult rv = NS_OK;
+ if (!newCard || !cardRow || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIMdbRowCellCursor> cursor;
+ nsCOMPtr<nsIMdbCell> cell;
+
+ rv = cardRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(cursor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mdb_column columnNumber;
+ char columnName[100];
+ struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr};
+ struct mdbYarn cellYarn;
+
+ do
+ {
+ rv = cursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &columnNumber, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!cell)
+ break;
+
+ // Get the value of the cell
+ cell->AliasYarn(m_mdbEnv, &cellYarn);
+ NS_ConvertUTF8toUTF16 value(static_cast<const char*>(cellYarn.mYarn_Buf),
+ cellYarn.mYarn_Fill);
+
+ if (!value.IsEmpty())
+ {
+ // Get the column of the cell
+ // Mork makes this so hard...
+ rv = m_mdbStore->TokenToString(m_mdbEnv, columnNumber, &colYarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char *name = PL_strndup(static_cast<char *>(colYarn.mYarn_Buf),
+ colYarn.mYarn_Fill);
+ newCard->SetPropertyAsAString(name, value);
+ PL_strfree(name);
+ }
+ } while (true);
+
+ uint32_t key = 0;
+ rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0);
+ if (NS_SUCCEEDED(rv))
+ newCard->SetPropertyAsUint32(kRecordKeyColumn, key);
+
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::GetListCardFromDB(nsIAbCard *listCard, nsIMdbRow* listRow)
+{
+ nsresult err = NS_OK;
+ if (!listCard || !listRow)
+ return NS_ERROR_NULL_POINTER;
+
+ nsAutoString tempString;
+
+ err = GetStringColumn(listRow, m_ListNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ listCard->SetDisplayName(tempString);
+ listCard->SetLastName(tempString);
+ }
+ err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ listCard->SetPropertyAsAString(kNicknameProperty, tempString);
+ }
+ err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ listCard->SetPropertyAsAString(kNotesProperty, tempString);
+ }
+ uint32_t key = 0;
+ err = GetIntColumn(listRow, m_RecordKeyColumnToken, &key, 0);
+ if (NS_SUCCEEDED(err))
+ listCard->SetPropertyAsUint32(kRecordKeyColumn, key);
+ return err;
+}
+
+nsresult nsAddrDatabase::GetListFromDB(nsIAbDirectory *newList, nsIMdbRow* listRow)
+{
+ nsresult err = NS_OK;
+ if (!newList || !listRow || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsAutoString tempString;
+
+ err = GetStringColumn(listRow, m_ListNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ newList->SetDirName(tempString);
+ }
+ err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ newList->SetListNickName(tempString);
+ }
+ err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ newList->SetDescription(tempString);
+ }
+
+ nsCOMPtr<nsIAbMDBDirectory> dbnewList(do_QueryInterface(newList, &err));
+ NS_ENSURE_SUCCESS(err, err);
+
+ uint32_t totalAddress = GetListAddressTotal(listRow);
+ uint32_t pos;
+ for (pos = 1; pos <= totalAddress; ++pos)
+ {
+ mdb_token listAddressColumnToken;
+ mdb_id rowID;
+
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ nsCOMPtr <nsIMdbRow> cardRow;
+ err = GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0);
+ NS_ENSURE_SUCCESS(err, err);
+ err = GetCardRowByRowID(rowID, getter_AddRefs(cardRow));
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (cardRow)
+ {
+ nsCOMPtr<nsIAbCard> card;
+ err = CreateABCard(cardRow, 0, getter_AddRefs(card));
+
+ if(NS_SUCCEEDED(err))
+ dbnewList->AddAddressToList(card);
+ }
+// NS_IF_ADDREF(card);
+ }
+
+ return err;
+}
+
+class nsAddrDBEnumerator : public nsISimpleEnumerator, public nsIAddrDBListener
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+ NS_DECL_NSIADDRDBLISTENER
+ // nsAddrDBEnumerator methods:
+
+ nsAddrDBEnumerator(nsAddrDatabase* aDb);
+ void Clear();
+protected:
+ virtual ~nsAddrDBEnumerator();
+ RefPtr<nsAddrDatabase> mDb;
+ nsIMdbTable *mDbTable;
+ nsCOMPtr<nsIMdbTableRowCursor> mRowCursor;
+ nsCOMPtr<nsIMdbRow> mCurrentRow;
+ mdb_pos mRowPos;
+};
+
+nsAddrDBEnumerator::nsAddrDBEnumerator(nsAddrDatabase* aDb)
+ : mDb(aDb),
+ mDbTable(aDb->GetPabTable()),
+ mRowPos(-1)
+{
+ if (aDb)
+ aDb->AddListener(this);
+}
+
+nsAddrDBEnumerator::~nsAddrDBEnumerator()
+{
+ Clear();
+}
+
+void nsAddrDBEnumerator::Clear()
+{
+ mRowCursor = nullptr;
+ mCurrentRow = nullptr;
+ mDbTable = nullptr;
+ if (mDb)
+ mDb->RemoveListener(this);
+}
+
+NS_IMPL_ISUPPORTS(nsAddrDBEnumerator, nsISimpleEnumerator, nsIAddrDBListener)
+
+NS_IMETHODIMP
+nsAddrDBEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ if (!mDbTable || !mDb->GetEnv())
+ {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ mDbTable->GetTableRowCursor(mDb->GetEnv(), mRowPos,
+ getter_AddRefs(rowCursor));
+ NS_ENSURE_TRUE(rowCursor, NS_ERROR_FAILURE);
+
+ mdbOid rowOid;
+ rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr);
+ while (rowOid.mOid_Id != (mdb_id)-1)
+ {
+ if (mDb->IsListRowScopeToken(rowOid.mOid_Scope) ||
+ mDb->IsCardRowScopeToken(rowOid.mOid_Scope))
+ {
+ *aResult = true;
+
+ return NS_OK;
+ }
+
+ if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope))
+ {
+ return NS_ERROR_FAILURE;
+ }
+
+ rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAddrDBEnumerator::GetNext(nsISupports **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (!mDbTable || !mDb->GetEnv())
+ {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!mRowCursor)
+ {
+ mDbTable->GetTableRowCursor(mDb->GetEnv(), -1,
+ getter_AddRefs(mRowCursor));
+ NS_ENSURE_TRUE(mRowCursor, NS_ERROR_FAILURE);
+ }
+
+ nsCOMPtr<nsIAbCard> resultCard;
+ mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow), &mRowPos);
+ while (mCurrentRow)
+ {
+ mdbOid rowOid;
+ if (NS_SUCCEEDED(mCurrentRow->GetOid(mDb->GetEnv(), &rowOid)))
+ {
+ nsresult rv;
+ if (mDb->IsListRowScopeToken(rowOid.mOid_Scope))
+ {
+ rv = mDb->CreateABListCard(mCurrentRow,
+ getter_AddRefs(resultCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (mDb->IsCardRowScopeToken(rowOid.mOid_Scope))
+ {
+ rv = mDb->CreateABCard(mCurrentRow, 0,
+ getter_AddRefs(resultCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope))
+ {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (resultCard)
+ {
+ return CallQueryInterface(resultCard, aResult);
+ }
+ }
+
+ mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow),
+ &mRowPos);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDBEnumerator::OnCardAttribChange(uint32_t abCode)
+{
+ return NS_OK;
+}
+
+/* void onCardEntryChange (in unsigned long aAbCode, in nsIAbCard aCard, in nsIAbDirectory aParent); */
+NS_IMETHODIMP nsAddrDBEnumerator::OnCardEntryChange(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent)
+{
+ return NS_OK;
+}
+
+/* void onListEntryChange (in unsigned long abCode, in nsIAbDirectory list); */
+NS_IMETHODIMP nsAddrDBEnumerator::OnListEntryChange(uint32_t abCode, nsIAbDirectory *list)
+{
+ return NS_OK;
+}
+
+/* void onAnnouncerGoingAway (); */
+NS_IMETHODIMP nsAddrDBEnumerator::OnAnnouncerGoingAway()
+{
+ Clear();
+ return NS_OK;
+}
+
+class nsListAddressEnumerator final : public nsISimpleEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsListAddressEnumerator methods:
+
+ nsListAddressEnumerator(nsAddrDatabase* aDb, mdb_id aRowID);
+
+protected:
+ ~nsListAddressEnumerator() {}
+ RefPtr<nsAddrDatabase> mDb;
+ nsIMdbTable *mDbTable;
+ nsCOMPtr<nsIMdbRow> mListRow;
+ mdb_id mListRowID;
+ uint32_t mAddressTotal;
+ uint16_t mAddressPos;
+};
+
+nsListAddressEnumerator::nsListAddressEnumerator(nsAddrDatabase* aDb,
+ mdb_id aRowID)
+ : mDb(aDb),
+ mDbTable(aDb->GetPabTable()),
+ mListRowID(aRowID),
+ mAddressPos(0)
+{
+ mDb->GetListRowByRowID(mListRowID, getter_AddRefs(mListRow));
+ mAddressTotal = aDb->GetListAddressTotal(mListRow);
+}
+
+NS_IMPL_ISUPPORTS(nsListAddressEnumerator, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+nsListAddressEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+
+ if (!mDbTable || !mDb->GetEnv())
+ {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // In some cases it is possible that GetAddressRowByPos returns success,
+ // but currentRow is null. This is typically due to the fact that a card
+ // has been deleted from the parent and not the list. Whilst we have fixed
+ // that there are still a few dbs around there that we need to support
+ // correctly. Therefore, whilst processing lists ensure that we don't return
+ // false if the only thing stopping us is a blank row, just skip it and try
+ // the next one.
+ while (mAddressPos < mAddressTotal)
+ {
+ nsCOMPtr<nsIMdbRow> currentRow;
+ nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos + 1,
+ getter_AddRefs(currentRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (currentRow)
+ {
+ *aResult = true;
+ break;
+ }
+
+ ++mAddressPos;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsListAddressEnumerator::GetNext(nsISupports **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (!mDbTable || !mDb->GetEnv())
+ {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (++mAddressPos <= mAddressTotal)
+ {
+ nsCOMPtr<nsIMdbRow> currentRow;
+ nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos,
+ getter_AddRefs(currentRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> resultCard;
+ rv = mDb->CreateABCard(currentRow, mListRowID,
+ getter_AddRefs(resultCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(resultCard, aResult);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsAddrDatabase::EnumerateCards(nsIAbDirectory *directory, nsISimpleEnumerator **result)
+{
+ nsAddrDBEnumerator* e = new nsAddrDBEnumerator(this);
+ m_dbDirectory = do_GetWeakReference(directory);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(e);
+ *result = e;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetMailingListsFromDB(nsIAbDirectory *parentDir)
+{
+ nsCOMPtr<nsIAbDirectory> resultList;
+ nsIMdbTableRowCursor* rowCursor = nullptr;
+ nsCOMPtr<nsIMdbRow> currentRow;
+ mdb_pos rowPos;
+ bool done = false;
+
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ m_dbDirectory = do_GetWeakReference(parentDir);
+
+ nsIMdbTable* dbTable = GetPabTable();
+
+ if (!dbTable)
+ return NS_ERROR_FAILURE;
+
+ dbTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);
+ if (!rowCursor)
+ return NS_ERROR_FAILURE;
+
+ while (!done)
+ {
+ nsresult rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
+ if (currentRow && NS_SUCCEEDED(rv))
+ {
+ mdbOid rowOid;
+
+ if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ if (IsListRowScopeToken(rowOid.mOid_Scope))
+ rv = CreateABList(currentRow, getter_AddRefs(resultList));
+ }
+ }
+ else
+ done = true;
+ }
+ NS_IF_RELEASE(rowCursor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::EnumerateListAddresses(nsIAbDirectory *directory, nsISimpleEnumerator **result)
+{
+ nsresult rv = NS_OK;
+ mdb_id rowID;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbdirectory(do_QueryInterface(directory,&rv));
+
+ if(NS_SUCCEEDED(rv))
+ {
+ dbdirectory->GetDbRowID((uint32_t*)&rowID);
+
+ nsListAddressEnumerator* e = new nsListAddressEnumerator(this, rowID);
+ m_dbDirectory = do_GetWeakReference(directory);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(e);
+ *result = e;
+ }
+ return rv;
+}
+
+nsresult nsAddrDatabase::CreateCardFromDeletedCardsTable(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result)
+{
+ if (!cardRow || !m_mdbEnv || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ if(NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIAbCard> personCard;
+ personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ InitCardFromRow(personCard, cardRow);
+ personCard->SetPropertyAsUint32(kRowIDProperty, rowID);
+
+ NS_IF_ADDREF(*result = personCard);
+ }
+
+ return rv;
+}
+
+nsresult nsAddrDatabase::CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result)
+{
+ if (!cardRow || !m_mdbEnv || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ if(NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIAbCard> personCard;
+ personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ InitCardFromRow(personCard, cardRow);
+ personCard->SetPropertyAsUint32(kRowIDProperty, rowID);
+
+ nsAutoCString id;
+ id.AppendInt(rowID);
+ personCard->SetLocalId(id);
+
+ nsCOMPtr<nsIAbDirectory> abDir(do_QueryReferent(m_dbDirectory));
+ if (abDir)
+ abDir->GetUuid(id);
+
+ personCard->SetDirectoryId(id);
+
+ NS_IF_ADDREF(*result = personCard);
+ }
+
+ return rv;
+}
+
+nsresult nsAddrDatabase::CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result)
+{
+ return CreateCard(cardRow, listRowID, result);
+}
+
+/* create a card for mailing list in the address book */
+nsresult nsAddrDatabase::CreateABListCard(nsIMdbRow* listRow, nsIAbCard **result)
+{
+ if (!listRow || !m_mdbEnv || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ char* listURI = nullptr;
+
+ nsAutoString fileName;
+ rv = m_dbName->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ listURI = PR_smprintf("%s%s/MailList%ld", kMDBDirectoryRoot, NS_ConvertUTF16toUTF8(fileName).get(), rowID);
+
+ nsCOMPtr<nsIAbCard> personCard;
+ nsCOMPtr<nsIAbMDBDirectory> dbm_dbDirectory(do_QueryReferent(m_dbDirectory,
+ &rv));
+ if (NS_SUCCEEDED(rv) && dbm_dbDirectory)
+ {
+ personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (personCard)
+ {
+ GetListCardFromDB(personCard, listRow);
+
+ personCard->SetPropertyAsUint32(kRowIDProperty, rowID);
+ personCard->SetIsMailList(true);
+ personCard->SetMailListURI(listURI);
+
+ nsAutoCString id;
+ id.AppendInt(rowID);
+ personCard->SetLocalId(id);
+
+ nsCOMPtr<nsIAbDirectory> abDir(do_QueryReferent(m_dbDirectory));
+ if (abDir)
+ abDir->GetUuid(id);
+ personCard->SetDirectoryId(id);
+ }
+
+ NS_IF_ADDREF(*result = personCard);
+ }
+ if (listURI)
+ PR_smprintf_free(listURI);
+
+ return rv;
+}
+
+/* create a sub directory for mailing list in the address book left pane */
+nsresult nsAddrDatabase::CreateABList(nsIMdbRow* listRow, nsIAbDirectory **result)
+{
+ nsresult rv = NS_OK;
+
+ if (!listRow || !m_mdbEnv || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ char* listURI = nullptr;
+
+ nsAutoString fileName;
+ m_dbName->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ listURI = PR_smprintf("%s%s/MailList%ld", kMDBDirectoryRoot, NS_ConvertUTF16toUTF8(fileName).get(), rowID);
+
+ nsCOMPtr<nsIAbDirectory> mailList;
+ nsCOMPtr<nsIAbMDBDirectory> dbm_dbDirectory(do_QueryReferent(m_dbDirectory,
+ &rv));
+ if (NS_SUCCEEDED(rv) && dbm_dbDirectory)
+ {
+ rv = dbm_dbDirectory->AddDirectory(listURI, getter_AddRefs(mailList));
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList (do_QueryInterface(mailList, &rv));
+
+ if (mailList)
+ {
+ // if we are using turbo, and we "exit" and restart with the same profile
+ // the current mailing list will still be in memory, so when we do
+ // GetResource() and QI, we'll get it again.
+ // in that scenario, the mailList that we pass in will already be
+ // be a mailing list, with a valid row and all the entries
+ // in that scenario, we can skip GetListFromDB(), which would have
+ // have added all the cards to the list again.
+ // see bug #134743
+ mdb_id existingID;
+ dbmailList->GetDbRowID(&existingID);
+ if (existingID != rowID) {
+ // Ensure IsMailList is set up first.
+ mailList->SetIsMailList(true);
+ GetListFromDB(mailList, listRow);
+ dbmailList->SetDbRowID(rowID);
+ }
+
+ dbm_dbDirectory->AddMailListToDirectory(mailList);
+ NS_IF_ADDREF(*result = mailList);
+ }
+ }
+
+ if (listURI)
+ PR_smprintf_free(listURI);
+
+ return rv;
+}
+
+nsresult nsAddrDatabase::GetCardRowByRowID(mdb_id rowID, nsIMdbRow **dbRow)
+{
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+ rowOid.mOid_Id = rowID;
+
+ return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow);
+}
+
+nsresult nsAddrDatabase::GetListRowByRowID(mdb_id rowID, nsIMdbRow **dbRow)
+{
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+ rowOid.mOid_Id = rowID;
+
+ return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow);
+}
+
+nsresult nsAddrDatabase::GetRowFromAttribute(const char *aName,
+ const nsACString &aUTF8Value,
+ bool aCaseInsensitive,
+ nsIMdbRow **aCardRow,
+ mdb_pos *aRowPos)
+{
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ENSURE_ARG_POINTER(aCardRow);
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdb_token token;
+ m_mdbStore->StringToToken(m_mdbEnv, aName, &token);
+ NS_ConvertUTF8toUTF16 newUnicodeString(aUTF8Value);
+
+ return GetRowForCharColumn(newUnicodeString.get(), token, true,
+ aCaseInsensitive, aCardRow, aRowPos);
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetCardFromAttribute(nsIAbDirectory *aDirectory,
+ const char *aName,
+ const nsACString &aUTF8Value,
+ bool aCaseInsensitive,
+ nsIAbCard **aCardResult)
+{
+ NS_ENSURE_ARG_POINTER(aCardResult);
+
+ m_dbDirectory = do_GetWeakReference(aDirectory);
+ nsCOMPtr<nsIMdbRow> cardRow;
+ if (NS_SUCCEEDED(GetRowFromAttribute(aName, aUTF8Value, aCaseInsensitive,
+ getter_AddRefs(cardRow), nullptr)) && cardRow)
+ return CreateABCard(cardRow, 0, aCardResult);
+
+ *aCardResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetCardsFromAttribute(nsIAbDirectory *aDirectory,
+ const char *aName,
+ const nsACString & aUTF8Value,
+ bool aCaseInsensitive,
+ nsISimpleEnumerator **cards)
+{
+ NS_ENSURE_ARG_POINTER(cards);
+
+ m_dbDirectory = do_GetWeakReference(aDirectory);
+ nsCOMPtr<nsIMdbRow> row;
+ bool done = false;
+ nsCOMArray<nsIAbCard> list;
+ nsCOMPtr<nsIAbCard> card;
+ mdb_pos rowPos = -1;
+
+ do
+ {
+ if (NS_SUCCEEDED(GetRowFromAttribute(aName, aUTF8Value, aCaseInsensitive,
+ getter_AddRefs(row), &rowPos)) && row)
+ {
+ if (NS_FAILED(CreateABCard(row, 0, getter_AddRefs(card))))
+ continue;
+ list.AppendObject(card);
+ }
+ else
+ done = true;
+ } while (!done);
+
+ return NS_NewArrayEnumerator(cards, list);
+}
+
+NS_IMETHODIMP nsAddrDatabase::AddListDirNode(nsIMdbRow * listRow)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoString parentURI;
+ rv = m_dbName->GetLeafName(parentURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ parentURI.Replace(0, 0, NS_LITERAL_STRING(kMDBDirectoryRoot));
+
+ nsCOMPtr<nsIAbDirectory> parentDir;
+ rv = abManager->GetDirectory(NS_ConvertUTF16toUTF8(parentURI),
+ getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (parentDir)
+ {
+ m_dbDirectory = do_GetWeakReference(parentDir);
+ nsCOMPtr<nsIAbDirectory> mailList;
+ rv = CreateABList(listRow, getter_AddRefs(mailList));
+ if (mailList)
+ {
+ nsCOMPtr<nsIAbMDBDirectory> dbparentDir(do_QueryInterface(parentDir, &rv));
+ if(NS_SUCCEEDED(rv))
+ dbparentDir->NotifyDirItemAdded(mailList);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::FindMailListbyUnicodeName(const char16_t *listName, bool *exist)
+{
+ nsAutoString unicodeString(listName);
+ ToLowerCase(unicodeString);
+
+ nsCOMPtr <nsIMdbRow> listRow;
+ nsresult rv = GetRowForCharColumn(unicodeString.get(),
+ m_LowerListNameColumnToken, false,
+ false, getter_AddRefs(listRow), nullptr);
+ *exist = (NS_SUCCEEDED(rv) && listRow);
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetCardCount(uint32_t *count)
+{
+ nsresult rv;
+ mdb_count c;
+ rv = m_mdbPabTable->GetCount(m_mdbEnv, &c);
+ if (NS_SUCCEEDED(rv))
+ *count = c - 1; // Don't count LastRecordKey
+
+ return rv;
+}
+
+bool
+nsAddrDatabase::HasRowButDeletedForCharColumn(const char16_t *unicodeStr, mdb_column findColumn, bool aIsCard, nsIMdbRow **aFindRow)
+{
+ if (!m_mdbStore || !aFindRow || !m_mdbEnv)
+ return false;
+
+ mdbYarn sourceYarn;
+
+ NS_ConvertUTF16toUTF8 UTF8String(unicodeStr);
+ sourceYarn.mYarn_Buf = (void *) UTF8String.get();
+ sourceYarn.mYarn_Fill = UTF8String.Length();
+ sourceYarn.mYarn_Form = 0;
+ sourceYarn.mYarn_Size = sourceYarn.mYarn_Fill;
+
+ mdbOid outRowId;
+ nsresult rv;
+
+ if (aIsCard)
+ {
+ rv = m_mdbStore->FindRow(m_mdbEnv, m_CardRowScopeToken,
+ findColumn, &sourceYarn, &outRowId, aFindRow);
+
+ // no such card, so bail out early
+ if (NS_FAILED(rv) || !*aFindRow)
+ return false;
+
+ // we might not have loaded the "delete cards" table yet
+ // so do that (but don't create it, if we don't have one),
+ // so we can see if the row is really a delete card.
+ if (!m_mdbDeletedCardsTable)
+ rv = InitDeletedCardsTable(false);
+
+ // if still no deleted cards table, there are no deleted cards
+ if (!m_mdbDeletedCardsTable)
+ return false;
+
+ mdb_bool hasRow = false;
+ rv = m_mdbDeletedCardsTable->HasRow(m_mdbEnv, *aFindRow, &hasRow);
+ return (NS_SUCCEEDED(rv) && hasRow);
+ }
+
+ rv = m_mdbStore->FindRow(m_mdbEnv, m_ListRowScopeToken,
+ findColumn, &sourceYarn, &outRowId, aFindRow);
+ return (NS_SUCCEEDED(rv) && *aFindRow);
+}
+
+/* @param aRowPos Contains the row position for multiple calls. Should be
+ * instantiated to -1 on the first call. Or can be null
+ * if you are not making multiple calls.
+ */
+nsresult
+nsAddrDatabase::GetRowForCharColumn(const char16_t *unicodeStr,
+ mdb_column findColumn, bool aIsCard,
+ bool aCaseInsensitive,
+ nsIMdbRow **aFindRow,
+ mdb_pos *aRowPos)
+{
+ NS_ENSURE_ARG_POINTER(unicodeStr);
+ NS_ENSURE_ARG_POINTER(aFindRow);
+ NS_ENSURE_TRUE(m_mdbEnv && m_mdbPabTable, NS_ERROR_NULL_POINTER);
+
+ *aFindRow = nullptr;
+
+ // see bug #198303
+ // the addition of the m_mdbDeletedCardsTable table has complicated life in the addressbook
+ // (it was added for palm sync). until we fix the underlying problem, we have to jump through hoops
+ // in order to know if we have a row (think card) for a given column value (think email=foo@bar.com)
+ // there are 4 scenarios:
+ // 1) no cards with a match
+ // 2) at least one deleted card with a match, but no non-deleted cards
+ // 3) at least one non-deleted card with a match, but no deleted cards
+ // 4) at least one deleted card, and one non-deleted card with a match.
+ //
+ // if we have no cards that match (FindRow() returns nothing), we can bail early
+ // but if FindRow() returns something, we have to check if it is in the deleted table
+ // if not in the deleted table we can return the row (we found a non-deleted card)
+ // but if so, we have to search through the table of non-deleted cards
+ // for a match. If we find one, we return it. but if not, we report that there are no
+ // non-deleted cards. This is the expensive part. The worse case scenario is to have
+ // deleted lots of cards, and then have a lot of non-deleted cards.
+ // we'd have to call FindRow(), HasRow(), and then search the list of non-deleted cards
+ // each time we call GetRowForCharColumn().
+ if (!aRowPos && !HasRowButDeletedForCharColumn(unicodeStr, findColumn, aIsCard, aFindRow))
+ {
+ // If we have a row, it's the row for the non-delete card, so return NS_OK.
+ // If we don't have a row, there are two possible conditions: either the
+ // card does not exist, or we are doing case-insensitive searching and the
+ // value isn't lowercase.
+
+ // Valid result, return.
+ if (*aFindRow)
+ return NS_OK;
+
+ // We definitely don't have anything at this point if case-sensitive.
+ if (!aCaseInsensitive)
+ return NS_ERROR_FAILURE;
+ }
+
+ // check if there is a non-deleted card
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ mdb_pos rowPos = -1;
+ bool done = false;
+ nsCOMPtr<nsIMdbRow> currentRow;
+ nsAutoString columnValue;
+
+ if (aRowPos)
+ rowPos = *aRowPos;
+
+ mdb_scope targetScope = aIsCard ? m_CardRowScopeToken : m_ListRowScopeToken;
+
+ m_mdbPabTable->GetTableRowCursor(m_mdbEnv, rowPos, getter_AddRefs(rowCursor));
+ if (!rowCursor)
+ return NS_ERROR_FAILURE;
+
+ while (!done)
+ {
+ nsresult rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
+ if (currentRow && NS_SUCCEEDED(rv))
+ {
+ mdbOid rowOid;
+ if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)) && (rowOid.mOid_Scope == targetScope))
+ {
+ rv = GetStringColumn(currentRow, findColumn, columnValue);
+
+ bool equals = aCaseInsensitive ?
+ columnValue.Equals(unicodeStr, nsCaseInsensitiveStringComparator()) :
+ columnValue.Equals(unicodeStr);
+
+ if (NS_SUCCEEDED(rv) && equals)
+ {
+ NS_IF_ADDREF(*aFindRow = currentRow);
+ if (aRowPos)
+ *aRowPos = rowPos;
+ return NS_OK;
+ }
+ }
+ }
+ else
+ done = true;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::DeleteRow(nsIMdbTable* dbTable, nsIMdbRow* dbRow)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = dbRow->CutAllColumns(m_mdbEnv);
+ err = dbTable->CutRow(m_mdbEnv, dbRow);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
diff --git a/mailnews/addrbook/src/nsAddrDatabase.h b/mailnews/addrbook/src/nsAddrDatabase.h
new file mode 100644
index 000000000..3b4e4eee6
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddrDatabase.h
@@ -0,0 +1,439 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsAddrDatabase_H_
+#define _nsAddrDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "nsIAddrDatabase.h"
+#include "mdb.h"
+#include "nsStringGlue.h"
+#include "nsIAddrDBListener.h"
+#include "nsCOMPtr.h"
+#include "nsTObserverArray.h"
+#include "nsWeakPtr.h"
+#include "nsIWeakReferenceUtils.h"
+
+typedef enum
+{
+ AB_NotifyInserted,
+ AB_NotifyDeleted,
+ AB_NotifyPropertyChanged
+} AB_NOTIFY_CODE;
+
+class nsAddrDatabase : public nsIAddrDatabase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIADDRDBANNOUNCER
+ //////////////////////////////////////////////////////////////////////////////
+ // nsIAddrDatabase methods:
+
+ NS_IMETHOD GetDbPath(nsIFile * *aDbPath) override;
+ NS_IMETHOD SetDbPath(nsIFile * aDbPath) override;
+ NS_IMETHOD Open(nsIFile *aMabFile, bool aCreate, bool upgrading, nsIAddrDatabase **pCardDB) override;
+ NS_IMETHOD Close(bool forceCommit) override;
+ NS_IMETHOD OpenMDB(nsIFile *dbName, bool create) override;
+ NS_IMETHOD CloseMDB(bool commit) override;
+ NS_IMETHOD Commit(uint32_t commitType) override;
+ NS_IMETHOD ForceClosed() override;
+
+ NS_IMETHOD CreateNewCardAndAddToDB(nsIAbCard *newCard, bool notify, nsIAbDirectory *parent) override;
+ NS_IMETHOD CreateNewListCardAndAddToDB(nsIAbDirectory *list, uint32_t listRowID, nsIAbCard *newCard, bool notify) override;
+ NS_IMETHOD CreateMailListAndAddToDB(nsIAbDirectory *newList, bool notify, nsIAbDirectory *parent) override;
+ NS_IMETHOD EnumerateCards(nsIAbDirectory *directory, nsISimpleEnumerator **result) override;
+ NS_IMETHOD GetMailingListsFromDB(nsIAbDirectory *parentDir) override;
+ NS_IMETHOD EnumerateListAddresses(nsIAbDirectory *directory, nsISimpleEnumerator **result) override;
+ NS_IMETHOD DeleteCard(nsIAbCard *newCard, bool notify, nsIAbDirectory *parent) override;
+ NS_IMETHOD EditCard(nsIAbCard *card, bool notify, nsIAbDirectory *parent) override;
+ NS_IMETHOD ContainsCard(nsIAbCard *card, bool *hasCard) override;
+ NS_IMETHOD DeleteMailList(nsIAbDirectory *aMailList, nsIAbDirectory *aParent) override;
+ NS_IMETHOD EditMailList(nsIAbDirectory *mailList, nsIAbCard *listCard, bool notify) override;
+ NS_IMETHOD ContainsMailList(nsIAbDirectory *mailList, bool *hasCard) override;
+ NS_IMETHOD DeleteCardFromMailList(nsIAbDirectory *mailList, nsIAbCard *card, bool aNotify) override;
+ NS_IMETHOD GetCardFromAttribute(nsIAbDirectory *aDirectory, const char *aName,
+ const nsACString &aValue,
+ bool aCaseInsensitive, nsIAbCard **card) override;
+ NS_IMETHOD GetCardsFromAttribute(nsIAbDirectory *aDirectory,
+ const char *aName,
+ const nsACString & uUTF8Value,
+ bool aCaseInsensitive,
+ nsISimpleEnumerator **cards) override;
+ NS_IMETHOD GetNewRow(nsIMdbRow * *newRow) override;
+ NS_IMETHOD GetNewListRow(nsIMdbRow * *newRow) override;
+ NS_IMETHOD AddCardRowToDB(nsIMdbRow *newRow) override;
+ NS_IMETHOD AddLdifListMember(nsIMdbRow* row, const char * value) override;
+
+ NS_IMETHOD GetDeletedCardList(nsIArray **aResult) override;
+ NS_IMETHOD GetDeletedCardCount(uint32_t *aCount) override;
+ NS_IMETHOD PurgeDeletedCardTable();
+
+ NS_IMETHOD AddFirstName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_FirstNameColumnToken, value); }
+
+ NS_IMETHOD AddLastName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_LastNameColumnToken, value); }
+
+ NS_IMETHOD AddPhoneticFirstName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_PhoneticFirstNameColumnToken, value); }
+
+ NS_IMETHOD AddPhoneticLastName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_PhoneticLastNameColumnToken, value); }
+
+ NS_IMETHOD AddDisplayName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_DisplayNameColumnToken, value); }
+
+ NS_IMETHOD AddNickName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_NickNameColumnToken, value); }
+
+ NS_IMETHOD AddPrimaryEmail(nsIMdbRow * row, const char * value) override;
+
+ NS_IMETHOD Add2ndEmail(nsIMdbRow * row, const char * value) override;
+
+ NS_IMETHOD AddPreferMailFormat(nsIMdbRow * row, uint32_t value) override
+ { return AddIntColumn(row, m_MailFormatColumnToken, value); }
+
+ NS_IMETHOD AddPopularityIndex(nsIMdbRow * row, uint32_t value) override
+ { return AddIntColumn(row, m_PopularityIndexColumnToken, value); }
+
+ NS_IMETHOD AddWorkPhone(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkPhoneColumnToken, value); }
+
+ NS_IMETHOD AddHomePhone(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomePhoneColumnToken, value); }
+
+ NS_IMETHOD AddFaxNumber(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_FaxColumnToken, value); }
+
+ NS_IMETHOD AddPagerNumber(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_PagerColumnToken, value); }
+
+ NS_IMETHOD AddCellularNumber(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_CellularColumnToken, value); }
+
+ NS_IMETHOD AddWorkPhoneType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkPhoneTypeColumnToken, value); }
+
+ NS_IMETHOD AddHomePhoneType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomePhoneTypeColumnToken, value); }
+
+ NS_IMETHOD AddFaxNumberType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_FaxTypeColumnToken, value); }
+
+ NS_IMETHOD AddPagerNumberType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_PagerTypeColumnToken, value); }
+
+ NS_IMETHOD AddCellularNumberType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_CellularTypeColumnToken, value); }
+
+ NS_IMETHOD AddHomeAddress(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeAddressColumnToken, value); }
+
+ NS_IMETHOD AddHomeAddress2(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeAddress2ColumnToken, value); }
+
+ NS_IMETHOD AddHomeCity(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeCityColumnToken, value); }
+
+ NS_IMETHOD AddHomeState(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeStateColumnToken, value); }
+
+ NS_IMETHOD AddHomeZipCode(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeZipCodeColumnToken, value); }
+
+ NS_IMETHOD AddHomeCountry(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeCountryColumnToken, value); }
+
+ NS_IMETHOD AddWorkAddress(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkAddressColumnToken, value); }
+
+ NS_IMETHOD AddWorkAddress2(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkAddress2ColumnToken, value); }
+
+ NS_IMETHOD AddWorkCity(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkCityColumnToken, value); }
+
+ NS_IMETHOD AddWorkState(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkStateColumnToken, value); }
+
+ NS_IMETHOD AddWorkZipCode(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkZipCodeColumnToken, value); }
+
+ NS_IMETHOD AddWorkCountry(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkCountryColumnToken, value); }
+
+ NS_IMETHOD AddJobTitle(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_JobTitleColumnToken, value); }
+
+ NS_IMETHOD AddDepartment(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_DepartmentColumnToken, value); }
+
+ NS_IMETHOD AddCompany(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_CompanyColumnToken, value); }
+
+ NS_IMETHOD AddAimScreenName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_AimScreenNameColumnToken, value); }
+
+ NS_IMETHOD AddAnniversaryYear(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_AnniversaryYearColumnToken, value); }
+
+ NS_IMETHOD AddAnniversaryMonth(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_AnniversaryMonthColumnToken, value); }
+
+ NS_IMETHOD AddAnniversaryDay(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_AnniversaryDayColumnToken, value); }
+
+ NS_IMETHOD AddSpouseName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_SpouseNameColumnToken, value); }
+
+ NS_IMETHOD AddFamilyName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_FamilyNameColumnToken, value); }
+
+ NS_IMETHOD AddDefaultAddress(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_DefaultAddressColumnToken, value); }
+
+ NS_IMETHOD AddCategory(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_CategoryColumnToken, value); }
+
+ NS_IMETHOD AddWebPage1(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WebPage1ColumnToken, value); }
+
+ NS_IMETHOD AddWebPage2(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WebPage2ColumnToken, value); }
+
+ NS_IMETHOD AddBirthYear(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_BirthYearColumnToken, value); }
+
+ NS_IMETHOD AddBirthMonth(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_BirthMonthColumnToken, value); }
+
+ NS_IMETHOD AddBirthDay(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_BirthDayColumnToken, value); }
+
+ NS_IMETHOD AddCustom1(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_Custom1ColumnToken, value); }
+
+ NS_IMETHOD AddCustom2(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_Custom2ColumnToken, value); }
+
+ NS_IMETHOD AddCustom3(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_Custom3ColumnToken, value); }
+
+ NS_IMETHOD AddCustom4(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_Custom4ColumnToken, value); }
+
+ NS_IMETHOD AddNotes(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_NotesColumnToken, value); }
+
+ NS_IMETHOD AddListName(nsIMdbRow * row, const char * value) override;
+
+ NS_IMETHOD AddListNickName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_ListNickNameColumnToken, value); }
+
+ NS_IMETHOD AddListDescription(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_ListDescriptionColumnToken, value); }
+
+
+ NS_IMETHOD AddListDirNode(nsIMdbRow * listRow) override;
+
+ NS_IMETHOD FindMailListbyUnicodeName(const char16_t *listName, bool *exist) override;
+
+ NS_IMETHOD GetCardCount(uint32_t *count) override;
+
+ NS_IMETHOD SetCardValue(nsIAbCard *card, const char *name, const char16_t *value, bool notify) override;
+ NS_IMETHOD GetCardValue(nsIAbCard *card, const char *name, char16_t **value) override;
+ // nsAddrDatabase methods:
+
+ nsAddrDatabase();
+
+ void GetMDBFactory(nsIMdbFactory ** aMdbFactory);
+ nsIMdbEnv *GetEnv() {return m_mdbEnv;}
+ uint32_t GetCurVersion();
+ nsIMdbTableRowCursor *GetTableRowCursor();
+ nsIMdbTable *GetPabTable() {return m_mdbPabTable;}
+
+ static nsAddrDatabase* FindInCache(nsIFile *dbName);
+
+ static void CleanupCache();
+
+ nsresult CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result);
+ nsresult CreateABListCard(nsIMdbRow* listRow, nsIAbCard **result);
+ nsresult CreateABList(nsIMdbRow* listRow, nsIAbDirectory **result);
+
+ bool IsListRowScopeToken(mdb_scope scope) { return (scope == m_ListRowScopeToken) ? true: false; }
+ bool IsCardRowScopeToken(mdb_scope scope) { return (scope == m_CardRowScopeToken) ? true: false; }
+ bool IsDataRowScopeToken(mdb_scope scope) { return (scope == m_DataRowScopeToken) ? true: false; }
+ nsresult GetCardRowByRowID(mdb_id rowID, nsIMdbRow **dbRow);
+ nsresult GetListRowByRowID(mdb_id rowID, nsIMdbRow **dbRow);
+
+ uint32_t GetListAddressTotal(nsIMdbRow* listRow);
+ nsresult GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos, nsIMdbRow** cardRow);
+
+ NS_IMETHOD AddListCardColumnsToRow(nsIAbCard *aPCard, nsIMdbRow *aPListRow, uint32_t aPos, nsIAbCard** aPNewCard, bool aInMailingList, nsIAbDirectory *aParent, nsIAbDirectory *aRoot) override;
+ NS_IMETHOD InitCardFromRow(nsIAbCard *aNewCard, nsIMdbRow* aCardRow) override;
+ NS_IMETHOD SetListAddressTotal(nsIMdbRow* aListRow, uint32_t aTotal) override;
+ NS_IMETHOD FindRowByCard(nsIAbCard * card,nsIMdbRow **aRow) override;
+
+protected:
+ virtual ~nsAddrDatabase();
+
+ static void RemoveFromCache(nsAddrDatabase* pAddrDB);
+ bool MatchDbName(nsIFile *dbName); // returns TRUE if they match
+
+ void YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult);
+ void GetCharStringYarn(char* str, struct mdbYarn* strYarn);
+ void GetStringYarn(const nsAString & aStr, struct mdbYarn* strYarn);
+ void GetIntYarn(uint32_t nValue, struct mdbYarn* intYarn);
+ nsresult AddCharStringColumn(nsIMdbRow* cardRow, mdb_column inColumn, const char* str);
+ nsresult AddStringColumn(nsIMdbRow* aCardRow, mdb_column aInColumn, const nsAString & aStr);
+ nsresult AddIntColumn(nsIMdbRow* cardRow, mdb_column inColumn, uint32_t nValue);
+ nsresult AddBoolColumn(nsIMdbRow* cardRow, mdb_column inColumn, bool bValue);
+ nsresult GetStringColumn(nsIMdbRow *cardRow, mdb_token outToken, nsString& str);
+ nsresult GetIntColumn(nsIMdbRow *cardRow, mdb_token outToken,
+ uint32_t* pValue, uint32_t defaultValue);
+ nsresult GetBoolColumn(nsIMdbRow *cardRow, mdb_token outToken, bool* pValue);
+ nsresult GetListCardFromDB(nsIAbCard *listCard, nsIMdbRow* listRow);
+ nsresult GetListFromDB(nsIAbDirectory *newCard, nsIMdbRow* listRow);
+ nsresult AddRecordKeyColumnToRow(nsIMdbRow *pRow);
+ nsresult AddAttributeColumnsToRow(nsIAbCard *card, nsIMdbRow *cardRow);
+ nsresult AddListAttributeColumnsToRow(nsIAbDirectory *list, nsIMdbRow *listRow, nsIAbDirectory *parent);
+ nsresult CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result);
+ nsresult CreateCardFromDeletedCardsTable(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result);
+ nsresult DeleteCardFromListRow(nsIMdbRow* pListRow, mdb_id cardRowID);
+ void DeleteCardFromAllMailLists(mdb_id cardRowID);
+ nsresult NotifyListEntryChange(uint32_t abCode, nsIAbDirectory *dir);
+
+ nsresult AddLowercaseColumn(nsIMdbRow * row, mdb_token columnToken, const char* utf8String);
+ nsresult GetRowFromAttribute(const char *aName, const nsACString &aUTF8Value,
+ bool aCaseInsensitive, nsIMdbRow **aCardRow,
+ mdb_pos *aRowPos);
+
+ static nsTArray<nsAddrDatabase*>* m_dbCache;
+ static nsTArray<nsAddrDatabase*>* GetDBCache();
+
+ // mdb bookkeeping stuff
+ nsresult InitExistingDB();
+ nsresult InitNewDB();
+ nsresult InitMDBInfo();
+ nsresult InitPabTable();
+ nsresult InitDeletedCardsTable(bool aCreate);
+ nsresult AddRowToDeletedCardsTable(nsIAbCard *card, nsIMdbRow **pCardRow);
+ nsresult DeleteRowFromDeletedCardsTable(nsIMdbRow *pCardRow);
+
+ nsresult InitLastRecorKey();
+ nsresult GetDataRow(nsIMdbRow **pDataRow);
+ nsresult GetLastRecordKey();
+ nsresult UpdateLastRecordKey();
+ nsresult CheckAndUpdateRecordKey();
+ nsresult UpdateLowercaseEmailListName();
+ nsresult ConvertAndAddLowercaseColumn(nsIMdbRow * row, mdb_token fromCol, mdb_token toCol);
+ nsresult AddUnicodeToColumn(nsIMdbRow * row, mdb_token colToken, mdb_token lowerCaseColToken, const char16_t* pUnicodeStr);
+
+ nsresult DeleteRow(nsIMdbTable* dbTable, nsIMdbRow* dbRow);
+
+ nsIMdbEnv *m_mdbEnv; // to be used in all the db calls.
+ nsIMdbStore *m_mdbStore;
+ nsIMdbTable *m_mdbPabTable;
+ nsIMdbTable *m_mdbDeletedCardsTable;
+ nsCOMPtr<nsIFile> m_dbName;
+ bool m_mdbTokensInitialized;
+ nsTObserverArray<nsIAddrDBListener*> m_ChangeListeners;
+
+ mdb_kind m_PabTableKind;
+ mdb_kind m_MailListTableKind;
+ mdb_kind m_DeletedCardsTableKind;
+
+ mdb_scope m_CardRowScopeToken;
+ mdb_scope m_ListRowScopeToken;
+ mdb_scope m_DataRowScopeToken;
+
+ mdb_token m_FirstNameColumnToken;
+ mdb_token m_LastNameColumnToken;
+ mdb_token m_PhoneticFirstNameColumnToken;
+ mdb_token m_PhoneticLastNameColumnToken;
+ mdb_token m_DisplayNameColumnToken;
+ mdb_token m_NickNameColumnToken;
+ mdb_token m_PriEmailColumnToken;
+ mdb_token m_2ndEmailColumnToken;
+ mdb_token m_DefaultEmailColumnToken;
+ mdb_token m_CardTypeColumnToken;
+ mdb_token m_WorkPhoneColumnToken;
+ mdb_token m_HomePhoneColumnToken;
+ mdb_token m_FaxColumnToken;
+ mdb_token m_PagerColumnToken;
+ mdb_token m_CellularColumnToken;
+ mdb_token m_WorkPhoneTypeColumnToken;
+ mdb_token m_HomePhoneTypeColumnToken;
+ mdb_token m_FaxTypeColumnToken;
+ mdb_token m_PagerTypeColumnToken;
+ mdb_token m_CellularTypeColumnToken;
+ mdb_token m_HomeAddressColumnToken;
+ mdb_token m_HomeAddress2ColumnToken;
+ mdb_token m_HomeCityColumnToken;
+ mdb_token m_HomeStateColumnToken;
+ mdb_token m_HomeZipCodeColumnToken;
+ mdb_token m_HomeCountryColumnToken;
+ mdb_token m_WorkAddressColumnToken;
+ mdb_token m_WorkAddress2ColumnToken;
+ mdb_token m_WorkCityColumnToken;
+ mdb_token m_WorkStateColumnToken;
+ mdb_token m_WorkZipCodeColumnToken;
+ mdb_token m_WorkCountryColumnToken;
+ mdb_token m_JobTitleColumnToken;
+ mdb_token m_DepartmentColumnToken;
+ mdb_token m_CompanyColumnToken;
+ mdb_token m_AimScreenNameColumnToken;
+ mdb_token m_AnniversaryYearColumnToken;
+ mdb_token m_AnniversaryMonthColumnToken;
+ mdb_token m_AnniversaryDayColumnToken;
+ mdb_token m_SpouseNameColumnToken;
+ mdb_token m_FamilyNameColumnToken;
+ mdb_token m_DefaultAddressColumnToken;
+ mdb_token m_CategoryColumnToken;
+ mdb_token m_WebPage1ColumnToken;
+ mdb_token m_WebPage2ColumnToken;
+ mdb_token m_BirthYearColumnToken;
+ mdb_token m_BirthMonthColumnToken;
+ mdb_token m_BirthDayColumnToken;
+ mdb_token m_Custom1ColumnToken;
+ mdb_token m_Custom2ColumnToken;
+ mdb_token m_Custom3ColumnToken;
+ mdb_token m_Custom4ColumnToken;
+ mdb_token m_NotesColumnToken;
+ mdb_token m_LastModDateColumnToken;
+ mdb_token m_RecordKeyColumnToken;
+ mdb_token m_LowerPriEmailColumnToken;
+ mdb_token m_Lower2ndEmailColumnToken;
+
+ mdb_token m_MailFormatColumnToken;
+ mdb_token m_PopularityIndexColumnToken;
+
+ mdb_token m_AddressCharSetColumnToken;
+ mdb_token m_LastRecordKeyColumnToken;
+
+ mdb_token m_ListNameColumnToken;
+ mdb_token m_ListNickNameColumnToken;
+ mdb_token m_ListDescriptionColumnToken;
+ mdb_token m_ListTotalColumnToken;
+ mdb_token m_LowerListNameColumnToken;
+
+ uint32_t m_LastRecordKey;
+ nsWeakPtr m_dbDirectory;
+ nsCOMPtr<nsIMdbFactory> mMdbFactory;
+
+private:
+ nsresult GetRowForCharColumn(const char16_t *unicodeStr,
+ mdb_column findColumn, bool bIsCard,
+ bool aCaseInsensitive, nsIMdbRow **findRow,
+ mdb_pos *aRowPos);
+ bool HasRowButDeletedForCharColumn(const char16_t *unicodeStr, mdb_column findColumn, bool aIsCard, nsIMdbRow **aFindRow);
+ nsresult OpenInternal(nsIFile *aMabFile, bool aCreate, nsIAddrDatabase **pCardDB);
+ nsresult AlertAboutCorruptMabFile(const char16_t *aOldFileName, const char16_t *aNewFileName);
+ nsresult AlertAboutLockedMabFile(const char16_t *aFileName);
+ nsresult DisplayAlert(const char16_t *titleName, const char16_t *alertStringName,
+ const char16_t **formatStrings, int32_t numFormatStrings);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAddrbook.manifest b/mailnews/addrbook/src/nsAddrbook.manifest
new file mode 100644
index 000000000..56efc3bda
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddrbook.manifest
@@ -0,0 +1,12 @@
+component {5b259db2-e451-4de9-8a6f-cfba91402973} nsAbAutoCompleteMyDomain.js
+contract @mozilla.org/autocomplete/search;1?name=mydomain {5b259db2-e451-4de9-8a6f-cfba91402973}
+component {2f946df9-114c-41fe-8899-81f10daf4f0c} nsAbAutoCompleteSearch.js
+contract @mozilla.org/autocomplete/search;1?name=addrbook {2f946df9-114c-41fe-8899-81f10daf4f0c}
+component {127b341a-bdda-4270-85e1-edff569a9b85} nsAbLDAPAttributeMap.js
+contract @mozilla.org/addressbook/ldap-attribute-map;1 {127b341a-bdda-4270-85e1-edff569a9b85}
+component {4ed7d5e1-8800-40da-9e78-c4f509d7ac5e} nsAbLDAPAttributeMap.js
+contract @mozilla.org/addressbook/ldap-attribute-map-service;1 {4ed7d5e1-8800-40da-9e78-c4f509d7ac5e}
+#ifdef MOZ_LDAP_XPCOM
+component {227e6482-fe9f-441f-9b7d-7b60375e7449} nsAbLDAPAutoCompleteSearch.js
+contract @mozilla.org/autocomplete/search;1?name=ldap {227e6482-fe9f-441f-9b7d-7b60375e7449}
+#endif \ No newline at end of file
diff --git a/mailnews/addrbook/src/nsDirPrefs.cpp b/mailnews/addrbook/src/nsDirPrefs.cpp
new file mode 100644
index 000000000..5e4c046b2
--- /dev/null
+++ b/mailnews/addrbook/src/nsDirPrefs.cpp
@@ -0,0 +1,1452 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* directory server preferences (used to be dirprefs.c in 4.x) */
+
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsDirPrefs.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsIAddrDatabase.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbManager.h"
+#include "nsIFile.h"
+#include "nsWeakReference.h"
+#include "nsIAbMDBDirectory.h"
+#if defined(MOZ_LDAP_XPCOM)
+#include "nsIAbLDAPDirectory.h"
+#endif
+#include "prmem.h"
+#include "prprf.h"
+#include "plstr.h"
+#include "nsQuickSort.h"
+#include "nsComponentManagerUtils.h"
+#include "msgCore.h"
+#include "nsStringGlue.h"
+
+#include <ctype.h>
+
+/*****************************************************************************
+ * Private definitions
+ */
+
+/* Default settings for site-configurable prefs */
+#define kDefaultPosition 1
+static bool dir_IsServerDeleted(DIR_Server * server);
+
+static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue);
+static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue);
+static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf);
+
+static char * dir_ConvertDescriptionToPrefName(DIR_Server * server);
+
+void DIR_SetFileName(char** filename, const char* leafName);
+static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue);
+static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref);
+static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname);
+static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList);
+
+static char *dir_CreateServerPrefName(DIR_Server *server);
+static void DIR_GetPrefsForOneServer(DIR_Server *server);
+
+static void DIR_InitServer(DIR_Server *server, DirectoryType dirType = (DirectoryType)0);
+static DIR_PrefId DIR_AtomizePrefName(const char *prefname);
+
+const int32_t DIR_POS_APPEND = -1;
+const int32_t DIR_POS_DELETE = -2;
+static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position);
+
+/* These two routines should be called to initialize and save
+ * directory preferences from the XP Java Script preferences
+ */
+static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list);
+static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList);
+
+static int32_t dir_UserId = 0;
+nsTArray<DIR_Server*> *dir_ServerList = nullptr;
+
+/*****************************************************************************
+ * Functions for creating the new back end managed DIR_Server list.
+ */
+class DirPrefObserver final : public nsSupportsWeakReference,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+private:
+ ~DirPrefObserver() {}
+};
+
+NS_IMPL_ISUPPORTS(DirPrefObserver, nsISupportsWeakReference, nsIObserver)
+
+NS_IMETHODIMP DirPrefObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_QueryInterface(aSubject));
+ nsCString strPrefName;
+ strPrefName.Assign(NS_ConvertUTF16toUTF8(aData));
+ const char *prefname = strPrefName.get();
+
+ DIR_PrefId id = DIR_AtomizePrefName(prefname);
+
+ // Just get out if we get nothing here - we don't need to do anything
+ if (id == idNone)
+ return NS_OK;
+
+ /* Check to see if the server is in the unified server list.
+ */
+ DIR_Server *server = dir_MatchServerPrefToServer(dir_ServerList, prefname);
+ if (server)
+ {
+ /* If the server is in the process of being saved, just ignore this
+ * change. The DIR_Server structure is not really changing.
+ */
+ if (server->savingServer)
+ return NS_OK;
+
+ /* If the pref that changed is the position, read it in. If the new
+ * position is zero, remove the server from the list.
+ */
+ if (id == idPosition)
+ {
+ int32_t position;
+
+ /* We must not do anything if the new position is the same as the
+ * position in the DIR_Server. This avoids recursion in cases
+ * where we are deleting the server.
+ */
+ prefBranch->GetIntPref(prefname, &position);
+ if (position != server->position)
+ {
+ server->position = position;
+ if (dir_IsServerDeleted(server))
+ DIR_SetServerPosition(dir_ServerList, server, DIR_POS_DELETE);
+ }
+ }
+
+ if (id == idDescription) {
+ // Ensure the local copy of the description is kept up to date.
+ PR_FREEIF(server->description);
+ server->description = DIR_GetLocalizedStringPref(prefname, nullptr);
+ }
+ }
+ /* If the server is not in the unified list, we may need to add it. Servers
+ * are only added when the position, serverName and description are valid.
+ */
+ else if (id == idPosition || id == idType || id == idDescription)
+ {
+ dir_ValidateAndAddNewServer(dir_ServerList, prefname);
+ }
+
+ return NS_OK;
+}
+
+// A pointer to the pref observer
+static DirPrefObserver *prefObserver = nullptr;
+
+static nsresult DIR_GetDirServers()
+{
+ nsresult rv = NS_OK;
+
+ if (!dir_ServerList)
+ {
+ /* we need to build the DIR_Server list */
+ rv = DIR_GetServerPreferences(&dir_ServerList);
+
+ /* Register the preference call back if necessary. */
+ if (NS_SUCCEEDED(rv) && !prefObserver)
+ {
+ nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ prefObserver = new DirPrefObserver();
+
+ if (!prefObserver)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(prefObserver);
+
+ pbi->AddObserver(PREF_LDAP_SERVER_TREE_NAME, prefObserver, true);
+ }
+ }
+ return rv;
+}
+
+nsTArray<DIR_Server*>* DIR_GetDirectories()
+{
+ if (!dir_ServerList)
+ DIR_GetDirServers();
+ return dir_ServerList;
+}
+
+DIR_Server* DIR_GetServerFromList(const char* prefName)
+{
+ DIR_Server* result = nullptr;
+
+ if (!dir_ServerList)
+ DIR_GetDirServers();
+
+ if (dir_ServerList)
+ {
+ int32_t count = dir_ServerList->Length();
+ int32_t i;
+ for (i = 0; i < count; ++i)
+ {
+ DIR_Server *server = dir_ServerList->ElementAt(i);
+
+ if (server && strcmp(server->prefName, prefName) == 0)
+ {
+ result = server;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+static nsresult SavePrefsFile()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ return pPref->SavePrefFile(nullptr);
+}
+
+nsresult DIR_ShutDown() /* FEs should call this when the app is shutting down. It frees all DIR_Servers regardless of ref count values! */
+{
+ nsresult rv = SavePrefsFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DIR_DeleteServerList(dir_ServerList);
+ dir_ServerList = nullptr;
+
+ /* unregister the preference call back, if necessary.
+ * we need to do this as DIR_Shutdown() is called when switching profiles
+ * when using turbo. (see nsAbDirectoryDataSource::Observe())
+ * When switching profiles, prefs get unloaded and then re-loaded
+ * we don't want our callback to get called for all that.
+ * We'll reset our callback the first time DIR_GetDirServers() is called
+ * after we've switched profiles.
+ */
+ NS_IF_RELEASE(prefObserver);
+
+ return NS_OK;
+}
+
+nsresult DIR_ContainsServer(DIR_Server* pServer, bool *hasDir)
+{
+ if (dir_ServerList)
+ {
+ int32_t count = dir_ServerList->Length();
+ int32_t i;
+ for (i = 0; i < count; i++)
+ {
+ DIR_Server* server = dir_ServerList->ElementAt(i);
+ if (server == pServer)
+ {
+ *hasDir = true;
+ return NS_OK;
+ }
+ }
+ }
+ *hasDir = false;
+ return NS_OK;
+}
+
+nsresult DIR_AddNewAddressBook(const nsAString &dirName,
+ const nsACString &fileName,
+ const nsACString &uri,
+ DirectoryType dirType,
+ const nsACString &prefName,
+ DIR_Server** pServer)
+{
+ DIR_Server * server = (DIR_Server *) PR_Malloc(sizeof(DIR_Server));
+ if (!server)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ DIR_InitServer(server, dirType);
+ if (!dir_ServerList)
+ DIR_GetDirServers();
+ if (dir_ServerList)
+ {
+ server->description = ToNewCString(NS_ConvertUTF16toUTF8(dirName));
+ server->position = kDefaultPosition; // don't set position so alphabetic sort will happen.
+
+ if (!fileName.IsEmpty())
+ server->fileName = ToNewCString(fileName);
+ else if (dirType == PABDirectory)
+ DIR_SetFileName(&server->fileName, kPersonalAddressbook);
+ else if (dirType == LDAPDirectory)
+ DIR_SetFileName(&server->fileName, kMainLdapAddressBook);
+
+ if (dirType != PABDirectory) {
+ if (!uri.IsEmpty())
+ server->uri = ToNewCString(uri);
+ }
+
+ if (!prefName.IsEmpty())
+ server->prefName = ToNewCString(prefName);
+
+ dir_ServerList->AppendElement(server);
+
+ DIR_SavePrefsForOneServer(server);
+
+ *pServer = server;
+
+ // save new address book into pref file
+ return SavePrefsFile();
+ }
+ return NS_ERROR_FAILURE;
+}
+
+/*****************************************************************************
+ * Functions for creating DIR_Servers
+ */
+static void DIR_InitServer(DIR_Server *server, DirectoryType dirType)
+{
+ if (!server) {
+ NS_WARNING("DIR_InitServer: server parameter not initialized");
+ return;
+ }
+
+ memset(server, 0, sizeof(DIR_Server));
+ server->position = kDefaultPosition;
+ server->uri = nullptr;
+ server->savingServer = false;
+ server->dirType = dirType;
+}
+
+/* Function for setting the position of a server. Can be used to append,
+ * delete, or move a server in a server list.
+ *
+ * The third parameter specifies the new position the server is to occupy.
+ * The resulting position may differ depending on the lock state of the
+ * given server and other servers in the list. The following special values
+ * are supported:
+ * DIR_POS_APPEND - Appends the server to the end of the list. If the server
+ * is already in the list, does nothing.
+ * DIR_POS_DELETE - Deletes the given server from the list. Note that this
+ * does not cause the server structure to be freed.
+ *
+ * Returns true if the server list was re-sorted.
+ */
+static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position)
+ {
+ NS_ENSURE_TRUE(wholeList, false);
+
+ int32_t i, count, num;
+ bool resort = false;
+ DIR_Server *s=nullptr;
+
+ switch (position) {
+ case DIR_POS_APPEND:
+ /* Do nothing if the request is to append a server that is already
+ * in the list.
+ */
+ count = wholeList->Length();
+ for (i= 0; i < count; i++)
+ {
+ if ((s = wholeList->ElementAt(i)) != nullptr)
+ if (s == server)
+ return false;
+ }
+ /* In general, if there are any servers already in the list, set the
+ * position to the position of the last server plus one. If there
+ * are none, set it to position 1.
+ */
+ if (count > 0)
+ {
+ s = wholeList->ElementAt(count - 1);
+ server->position = s->position + 1;
+ }
+ else
+ server->position = 1;
+
+ wholeList->AppendElement(server);
+ break;
+
+ case DIR_POS_DELETE:
+ /* Remove the prefs corresponding to the given server. If the prefName
+ * value is nullptr, the server has never been saved and there are no
+ * prefs to remove.
+ */
+ if (server->prefName)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return false;
+
+ pPref->DeleteBranch(server->prefName);
+
+ // mark the server as deleted by setting its position to 0
+ DIR_SetIntPref(server->prefName, "position", 0, -1);
+ }
+
+ /* If the server is in the server list, remove it.
+ */
+ num = wholeList->IndexOf(server);
+ if (num >= 0)
+ {
+ /* The list does not need to be re-sorted if the server is the
+ * last one in the list.
+ */
+ count = wholeList->Length();
+ if (num == count - 1)
+ {
+ wholeList->RemoveElementAt(num);
+ }
+ else
+ {
+ resort = true;
+ wholeList->RemoveElement(server);
+ }
+ }
+ break;
+
+ default:
+ /* See if the server is already in the list.
+ */
+ count = wholeList->Length();
+ for (i= 0; i < count; i++)
+ {
+ if ((s = wholeList->ElementAt(i)) != nullptr)
+ if (s == server)
+ break;
+ }
+
+ /* If the server is not in the list, add it to the beginning and re-sort.
+ */
+ if (s == nullptr)
+ {
+ server->position = position;
+ wholeList->AppendElement(server);
+ resort = true;
+ }
+
+ /* Don't re-sort if the server is already in the requested position.
+ */
+ else if (server->position != position)
+ {
+ server->position = position;
+ wholeList->RemoveElement(server);
+ wholeList->AppendElement(server);
+ resort = true;
+ }
+ break;
+ }
+
+ /* Make sure our position changes get saved back to prefs
+ */
+ DIR_SaveServerPreferences(wholeList);
+
+ return resort;
+}
+
+/*****************************************************************************
+ * DIR_Server Callback Notification Functions
+ */
+
+/* dir_matchServerPrefToServer
+ *
+ * This function finds the DIR_Server in the unified DIR_Server list to which
+ * the given preference string belongs.
+ */
+static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref)
+{
+ DIR_Server *server;
+
+ int32_t count = wholeList->Length();
+ int32_t i;
+ for (i = 0; i < count; i++)
+ {
+ if ((server = wholeList->ElementAt(i)) != nullptr)
+ {
+ if (server->prefName && PL_strstr(pref, server->prefName) == pref)
+ {
+ char c = pref[PL_strlen(server->prefName)];
+ if (c == 0 || c == '.')
+ return server;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/* dir_ValidateAndAddNewServer
+ *
+ * This function verifies that the position, serverName and description values
+ * are set for the given prefName. If they are then it adds the server to the
+ * unified server list.
+ */
+static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname)
+{
+ bool rc = false;
+
+ const char *endname = PL_strchr(&fullprefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
+ if (endname)
+ {
+ char *prefname = (char *)PR_Malloc(endname - fullprefname + 1);
+ if (prefname)
+ {
+ int32_t dirType;
+ char *t1 = nullptr, *t2 = nullptr;
+
+ PL_strncpyz(prefname, fullprefname, endname - fullprefname + 1);
+
+ dirType = DIR_GetIntPref(prefname, "dirType", -1);
+ if (dirType != -1 &&
+ DIR_GetIntPref(prefname, "position", 0) != 0 &&
+ (t1 = DIR_GetLocalizedStringPref(prefname, "description")) != nullptr)
+ {
+ if (dirType == PABDirectory ||
+ (t2 = DIR_GetStringPref(prefname, "serverName", nullptr)) != nullptr)
+ {
+ DIR_Server *server = (DIR_Server *)PR_Malloc(sizeof(DIR_Server));
+ if (server)
+ {
+ DIR_InitServer(server, (DirectoryType)dirType);
+ server->prefName = prefname;
+ DIR_GetPrefsForOneServer(server);
+ DIR_SetServerPosition(wholeList, server, server->position);
+ rc = true;
+ }
+ PR_FREEIF(t2);
+ }
+ PR_Free(t1);
+ }
+ else
+ PR_Free(prefname);
+ }
+ }
+
+ return rc;
+}
+
+static DIR_PrefId DIR_AtomizePrefName(const char *prefname)
+{
+ if (!prefname)
+ return idNone;
+
+ DIR_PrefId rc = idNone;
+
+ /* Skip the "ldap_2.servers.<server-name>." portion of the string.
+ */
+ if (PL_strstr(prefname, PREF_LDAP_SERVER_TREE_NAME) == prefname)
+ {
+ prefname = PL_strchr(&prefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
+ if (!prefname)
+ return idNone;
+ else
+ prefname = prefname + 1;
+ }
+
+ switch (prefname[0]) {
+ case 'd':
+ switch (prefname[1]) {
+ case 'e': /* description */
+ rc = idDescription;
+ break;
+ case 'i': /* dirType */
+ rc = idType;
+ break;
+ }
+ break;
+
+ case 'f':
+ rc = idFileName;
+ break;
+
+ case 'p':
+ switch (prefname[1]) {
+ case 'o':
+ switch (prefname[2]) {
+ case 's': /* position */
+ rc = idPosition;
+ break;
+ }
+ break;
+ }
+ break;
+
+ case 'u': /* uri */
+ rc = idUri;
+ break;
+ }
+
+ return rc;
+}
+
+/*****************************************************************************
+ * Functions for destroying DIR_Servers
+ */
+
+/* this function determines if the passed in server is no longer part of the of
+ the global server list. */
+static bool dir_IsServerDeleted(DIR_Server * server)
+{
+ return (server && server->position == 0);
+}
+
+/* when the back end manages the server list, deleting a server just decrements its ref count,
+ in the old world, we actually delete the server */
+static void DIR_DeleteServer(DIR_Server *server)
+{
+ if (server)
+ {
+ /* when destroying the server check its clear flag to see if things need cleared */
+#ifdef XP_FileRemove
+ if (DIR_TestFlag(server, DIR_CLEAR_SERVER))
+ {
+ if (server->fileName)
+ XP_FileRemove (server->fileName, xpAddrBookNew);
+ }
+#endif /* XP_FileRemove */
+ PR_Free(server->prefName);
+ PR_Free(server->description);
+ PR_Free(server->fileName);
+ PR_Free(server->uri);
+ PR_Free(server);
+ }
+}
+
+nsresult DIR_DeleteServerFromList(DIR_Server *server)
+{
+ if (!server)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> dbPath;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ // close the database, as long as it isn't the special ones
+ // (personal addressbook and collected addressbook)
+ // which can never be deleted. There was a bug where we would slap in
+ // "abook.mab" as the file name for LDAP directories, which would cause a crash
+ // on delete of LDAP directories. this is just extra protection.
+ if (server->fileName &&
+ strcmp(server->fileName, kPersonalAddressbook) &&
+ strcmp(server->fileName, kCollectedAddressbook))
+ {
+ nsCOMPtr<nsIAddrDatabase> database;
+
+ rv = dbPath->AppendNative(nsDependentCString(server->fileName));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // close file before delete it
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory =
+ do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv) && addrDBFactory)
+ rv = addrDBFactory->Open(dbPath, false, true, getter_AddRefs(database));
+ if (database) /* database exists */
+ {
+ database->ForceClosed();
+ rv = dbPath->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsTArray<DIR_Server*> *dirList = DIR_GetDirectories();
+ DIR_SetServerPosition(dirList, server, DIR_POS_DELETE);
+ DIR_DeleteServer(server);
+
+ return SavePrefsFile();
+ }
+
+ return NS_ERROR_NULL_POINTER;
+}
+
+static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList)
+{
+ if (wholeList)
+ {
+ DIR_Server *server = nullptr;
+
+ /* TBD: Send notifications? */
+ int32_t count = wholeList->Length();
+ int32_t i;
+ for (i = count - 1; i >=0; i--)
+ {
+ server = wholeList->ElementAt(i);
+ if (server != nullptr)
+ DIR_DeleteServer(server);
+ }
+ delete wholeList;
+ }
+}
+
+/*****************************************************************************
+ * Functions for managing JavaScript prefs for the DIR_Servers
+ */
+
+static int
+comparePrefArrayMembers(const void* aElement1, const void* aElement2, void* aData)
+{
+ const char* element1 = *static_cast<const char* const *>(aElement1);
+ const char* element2 = *static_cast<const char* const *>(aElement2);
+ const uint32_t offset = *((const uint32_t*)aData);
+
+ // begin the comparison at |offset| chars into the string -
+ // this avoids comparing the "ldap_2.servers." portion of every element,
+ // which will always remain the same.
+ return strcmp(element1 + offset, element2 + offset);
+}
+
+static nsresult dir_GetChildList(const nsCString &aBranch,
+ uint32_t *aCount, char ***aChildList)
+{
+ uint32_t branchLen = aBranch.Length();
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefBranch) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = prefBranch->GetChildList(aBranch.get(), aCount, aChildList);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // traverse the list, and truncate all the descendant strings to just
+ // one branch level below the root branch.
+ for (uint32_t i = *aCount; i--; ) {
+ // The prefname we passed to GetChildList was of the form
+ // "ldap_2.servers." and we are returned the descendants
+ // in the form of "ldap_2.servers.servername.foo"
+ // But we want the prefbranch of the servername, so
+ // write a NUL character in to terminate the string early.
+ char *endToken = strchr((*aChildList)[i] + branchLen, '.');
+ if (endToken)
+ *endToken = '\0';
+ }
+
+ if (*aCount > 1) {
+ // sort the list, in preparation for duplicate entry removal
+ NS_QuickSort(*aChildList, *aCount, sizeof(char*), comparePrefArrayMembers, &branchLen);
+
+ // traverse the list and remove duplicate entries.
+ // we use two positions in the list; the current entry and the next
+ // entry; and perform a bunch of in-place ptr moves. so |cur| points
+ // to the last unique entry, and |next| points to some (possibly much
+ // later) entry to test, at any given point. we know we have >= 2
+ // elements in the list here, so we just init the two counters sensibly
+ // to begin with.
+ uint32_t cur = 0;
+ for (uint32_t next = 1; next < *aCount; ++next) {
+ // check if the elements are equal or unique
+ if (!comparePrefArrayMembers(&((*aChildList)[cur]), &((*aChildList)[next]), &branchLen)) {
+ // equal - just free & increment the next element ptr
+
+ free((*aChildList)[next]);
+ } else {
+ // cur & next are unique, so we need to shift the element.
+ // ++cur will point to the next free location in the
+ // reduced array (it's okay if that's == next)
+ (*aChildList)[++cur] = (*aChildList)[next];
+ }
+ }
+
+ // update the unique element count
+ *aCount = cur + 1;
+ }
+
+ return NS_OK;
+}
+
+static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsCString value;
+ nsAutoCString prefLocation(prefRoot);
+
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+
+ if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(value))))
+ {
+ /* unfortunately, there may be some prefs out there which look like this */
+ if (value.EqualsLiteral("(null)"))
+ {
+ if (defaultValue)
+ value = defaultValue;
+ else
+ value.Truncate();
+ }
+
+ if (value.IsEmpty())
+ {
+ rv = pPref->GetCharPref(prefLocation.get(), getter_Copies(value));
+ }
+ }
+ else
+ value = defaultValue;
+
+ return ToNewCString(value);
+}
+
+/**
+ * Get localized unicode string pref from properties file, convert into an UTF8 string
+ * since address book prefs store as UTF8 strings. So far there are 2 default
+ * prefs stored in addressbook.properties.
+ * "ldap_2.servers.pab.description"
+ * "ldap_2.servers.history.description"
+ */
+static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsAutoCString prefLocation(prefRoot);
+ if (prefLeaf) {
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+ }
+
+ nsString wvalue;
+ nsCOMPtr<nsIPrefLocalizedString> locStr;
+
+ rv = pPref->GetComplexValue(prefLocation.get(), NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(locStr));
+ if (NS_SUCCEEDED(rv))
+ rv = locStr->ToString(getter_Copies(wvalue));
+
+ char *value = nullptr;
+ if (!wvalue.IsEmpty())
+ {
+ value = ToNewCString(NS_ConvertUTF16toUTF8(wvalue));
+ }
+ else
+ {
+ // In TB 2 only some prefs had chrome:// URIs. We had code in place that would
+ // only get the localized string pref for the particular address books that
+ // were built-in.
+ // Additionally, nsIPrefBranch::getComplexValue will only get a non-user-set,
+ // non-locked pref value if it is a chrome:// URI and will get the string
+ // value at that chrome URI. This breaks extensions/autoconfig that want to
+ // set default pref values and allow users to change directory names.
+ //
+ // Now we have to support this, and so if for whatever reason we fail to get
+ // the localized version, then we try and get the non-localized version
+ // instead. If the string value is empty, then we'll just get the empty value
+ // back here.
+ rv = pPref->GetCharPref(prefLocation.get(), &value);
+ if (NS_FAILED(rv))
+ value = nullptr;
+ }
+
+ return value;
+}
+
+static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+
+ if (NS_FAILED(rv))
+ return defaultValue;
+
+ int32_t value;
+ nsAutoCString prefLocation(prefRoot);
+
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+
+ if (NS_FAILED(pPref->GetIntPref(prefLocation.get(), &value)))
+ value = defaultValue;
+
+ return value;
+}
+
+/* This will convert from the old preference that was a path and filename */
+/* to a just a filename */
+static void DIR_ConvertServerFileName(DIR_Server* pServer)
+{
+ char* leafName = pServer->fileName;
+ char* newLeafName = nullptr;
+#if defined(XP_WIN)
+ /* jefft -- bug 73349 This is to allow users share same address book.
+ * It only works if the user specify a full path filename.
+ */
+#ifdef XP_FileIsFullPath
+ if (! XP_FileIsFullPath(leafName))
+ newLeafName = XP_STRRCHR (leafName, '\\');
+#endif /* XP_FileIsFullPath */
+#else
+ newLeafName = strrchr(leafName, '/');
+#endif
+ pServer->fileName = newLeafName ? strdup(newLeafName + 1) : strdup(leafName);
+ if (leafName) PR_Free(leafName);
+}
+
+/* This will generate a correct filename and then remove the path.
+ * Note: we are assuming that the default name is in the native
+ * filesystem charset. The filename will be returned as a UTF8
+ * string.
+ */
+void DIR_SetFileName(char** fileName, const char* defaultName)
+{
+ if (!fileName)
+ return;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> dbPath;
+
+ *fileName = nullptr;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = dbPath->AppendNative(nsDependentCString(defaultName));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664);
+
+ nsAutoString realFileName;
+ rv = dbPath->GetLeafName(realFileName);
+
+ if (NS_SUCCEEDED(rv))
+ *fileName = ToNewUTF8String(realFileName);
+ }
+ }
+}
+
+/****************************************************************
+Helper function used to generate a file name from the description
+of a directory. Caller must free returned string.
+An extension is not applied
+*****************************************************************/
+
+static char * dir_ConvertDescriptionToPrefName(DIR_Server * server)
+{
+#define MAX_PREF_NAME_SIZE 25
+ char * fileName = nullptr;
+ char fileNameBuf[MAX_PREF_NAME_SIZE];
+ int32_t srcIndex = 0;
+ int32_t destIndex = 0;
+ int32_t numSrcBytes = 0;
+ const char * descr = nullptr;
+ if (server && server->description)
+ {
+ descr = server->description;
+ numSrcBytes = PL_strlen(descr);
+ while (srcIndex < numSrcBytes && destIndex < MAX_PREF_NAME_SIZE-1)
+ {
+ if (IS_DIGIT(descr[srcIndex]) || IS_ALPHA(descr[srcIndex]))
+ {
+ fileNameBuf[destIndex] = descr[srcIndex];
+ destIndex++;
+ }
+
+ srcIndex++;
+ }
+
+ fileNameBuf[destIndex] = '\0'; /* zero out the last character */
+ }
+
+ if (destIndex) /* have at least one character in the file name? */
+ fileName = strdup(fileNameBuf);
+
+ return fileName;
+}
+
+
+void DIR_SetServerFileName(DIR_Server *server)
+{
+ char * tempName = nullptr;
+ const char * prefName = nullptr;
+ uint32_t numHeaderBytes = 0;
+
+ if (server && (!server->fileName || !(*server->fileName)) )
+ {
+ PR_FREEIF(server->fileName); // might be one byte empty string.
+ /* make sure we have a pref name...*/
+ if (!server->prefName || !*server->prefName)
+ server->prefName = dir_CreateServerPrefName(server);
+
+ /* set default personal address book file name*/
+ if ((server->position == 1) && (server->dirType == PABDirectory))
+ server->fileName = strdup(kPersonalAddressbook);
+ else
+ {
+ /* now use the pref name as the file name since we know the pref name
+ will be unique */
+ prefName = server->prefName;
+ if (prefName && *prefName)
+ {
+ /* extract just the pref name part and not the ldap tree name portion from the string */
+ numHeaderBytes = PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1; /* + 1 for the '.' b4 the name */
+ if (PL_strlen(prefName) > numHeaderBytes)
+ tempName = strdup(prefName + numHeaderBytes);
+
+ if (tempName)
+ {
+ server->fileName = PR_smprintf("%s%s", tempName, kABFileName_CurrentSuffix);
+ PR_Free(tempName);
+ }
+ }
+ }
+
+ if (!server->fileName || !*server->fileName) /* when all else has failed, generate a default name */
+ {
+ if (server->dirType == LDAPDirectory)
+ DIR_SetFileName(&(server->fileName), kMainLdapAddressBook); /* generates file name with an ldap prefix */
+ else
+ DIR_SetFileName(&(server->fileName), kPersonalAddressbook);
+ }
+ }
+}
+
+static char *dir_CreateServerPrefName (DIR_Server *server)
+{
+ /* we are going to try to be smart in how we generate our server
+ pref name. We'll try to convert the description into a pref name
+ and then verify that it is unique. If it is unique then use it... */
+ char * leafName = dir_ConvertDescriptionToPrefName(server);
+ char * prefName = nullptr;
+ bool isUnique = false;
+
+ if (!leafName || !*leafName)
+ {
+ // we need to handle this in case the description has no alphanumeric chars
+ // it's very common for cjk users
+ leafName = strdup("_nonascii");
+ }
+
+ if (leafName)
+ {
+ int32_t uniqueIDCnt = 0;
+ char **children = nullptr;
+ /* we need to verify that this pref string name is unique */
+ prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s", leafName);
+ isUnique = false;
+ uint32_t prefCount;
+ nsresult rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."),
+ &prefCount, &children);
+ if (NS_SUCCEEDED(rv))
+ {
+ while (!isUnique && prefName)
+ {
+ isUnique = true; /* now flip the logic and assume we are unique until we find a match */
+ for (uint32_t i = 0; i < prefCount && isUnique; ++i)
+ {
+ if (!PL_strcasecmp(children[i], prefName)) /* are they the same branch? */
+ isUnique = false;
+ }
+ if (!isUnique) /* then try generating a new pref name and try again */
+ {
+ PR_smprintf_free(prefName);
+ prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s_%d", leafName, ++uniqueIDCnt);
+ }
+ } /* if we have a list of pref Names */
+
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children);
+ } /* while we don't have a unique name */
+
+ // fallback to "user_directory_N" form if we failed to verify
+ if (!isUnique && prefName)
+ {
+ PR_smprintf_free(prefName);
+ prefName = nullptr;
+ }
+
+ PR_Free(leafName);
+
+ } /* if leafName */
+
+ if (!prefName) /* last resort if we still don't have a pref name is to use user_directory string */
+ return PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".user_directory_%d", ++dir_UserId);
+ else
+ return prefName;
+}
+
+static void DIR_GetPrefsForOneServer(DIR_Server *server)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ char *prefstring = server->prefName;
+
+ // this call fills in tempstring with the position pref, and
+ // we then check to see if it's locked.
+ server->position = DIR_GetIntPref (prefstring, "position", kDefaultPosition);
+
+ // For default address books, this will get the name from the chrome
+ // file referenced, for other address books it'll just retrieve it from prefs
+ // as normal.
+ server->description = DIR_GetLocalizedStringPref(prefstring, "description");
+
+ server->dirType = (DirectoryType)DIR_GetIntPref (prefstring, "dirType", LDAPDirectory);
+
+ server->fileName = DIR_GetStringPref (prefstring, "filename", "");
+ // if we don't have a file name try and get one
+ if (!server->fileName || !*(server->fileName))
+ DIR_SetServerFileName (server);
+ if (server->fileName && *server->fileName)
+ DIR_ConvertServerFileName(server);
+
+ // the string "s" is the default uri ( <scheme> + "://" + <filename> )
+ nsCString s((server->dirType == PABDirectory || server->dirType == MAPIDirectory) ?
+#if defined(MOZ_LDAP_XPCOM)
+ kMDBDirectoryRoot : kLDAPDirectoryRoot);
+#else
+ // Fallback to the all directory root in the non-ldap enabled case.
+ kMDBDirectoryRoot : kAllDirectoryRoot);
+#endif
+ s.Append (server->fileName);
+ server->uri = DIR_GetStringPref (prefstring, "uri", s.get ());
+}
+
+static nsresult dir_GetPrefs(nsTArray<DIR_Server*> **list)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ (*list) = new nsTArray<DIR_Server*>();
+ if (!(*list))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ char **children;
+ uint32_t prefCount;
+
+ rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."),
+ &prefCount, &children);
+ if (NS_FAILED(rv))
+ return rv;
+
+ /* TBD: Temporary code to read broken "ldap" preferences tree.
+ * Remove line with if statement after M10.
+ */
+ if (dir_UserId == 0)
+ pPref->GetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", &dir_UserId);
+
+ for (uint32_t i = 0; i < prefCount; ++i)
+ {
+ DIR_Server *server;
+
+ server = (DIR_Server *)PR_Calloc(1, sizeof(DIR_Server));
+ if (server)
+ {
+ DIR_InitServer(server);
+ server->prefName = strdup(children[i]);
+ DIR_GetPrefsForOneServer(server);
+ if (server->description && server->description[0] &&
+ ((server->dirType == PABDirectory ||
+ server->dirType == MAPIDirectory ||
+ server->dirType == FixedQueryLDAPDirectory || // this one might go away
+ server->dirType == LDAPDirectory)))
+ {
+ if (!dir_IsServerDeleted(server))
+ {
+ (*list)->AppendElement(server);
+ }
+ else
+ DIR_DeleteServer(server);
+ }
+ else
+ {
+ DIR_DeleteServer(server);
+ }
+ }
+ }
+
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children);
+
+ return NS_OK;
+}
+
+// I don't think we care about locked positions, etc.
+void DIR_SortServersByPosition(nsTArray<DIR_Server*> *serverList)
+{
+ int i, j;
+ DIR_Server *server;
+
+ int count = serverList->Length();
+ for (i = 0; i < count - 1; i++)
+ {
+ for (j = i + 1; j < count; j++)
+ {
+ if (serverList->ElementAt(j)->position < serverList->ElementAt(i)->position)
+ {
+ server = serverList->ElementAt(i);
+ serverList->ReplaceElementAt(i, serverList->ElementAt(j));
+ serverList->ReplaceElementAt(j, server);
+ }
+ }
+ }
+}
+
+static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list)
+{
+ nsresult err;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &err));
+ if (NS_FAILED(err))
+ return err;
+
+ int32_t version = -1;
+ nsTArray<DIR_Server*> *newList = nullptr;
+
+ /* Update the ldap list version and see if there are old prefs to migrate. */
+ err = pPref->GetIntPref(PREF_LDAP_VERSION_NAME, &version);
+ NS_ENSURE_SUCCESS(err, err);
+
+ /* Find the new-style "ldap_2.servers" tree in prefs */
+ err = dir_GetPrefs(&newList);
+
+ if (version < kCurrentListVersion)
+ {
+ pPref->SetIntPref(PREF_LDAP_VERSION_NAME, kCurrentListVersion);
+ }
+
+ DIR_SortServersByPosition(newList);
+
+ *list = newList;
+
+ return err;
+}
+
+static void DIR_SetStringPref(const char *prefRoot, const char *prefLeaf, const char *value, const char *defaultValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCString defaultPref;
+ nsAutoCString prefLocation(prefRoot);
+
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+
+ if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(defaultPref))))
+ {
+ /* If there's a default pref, just set ours in and let libpref worry
+ * about potential defaults in all.js
+ */
+ if (value) /* added this check to make sure we have a value before we try to set it..*/
+ rv = pPref->SetCharPref (prefLocation.get(), value);
+ else
+ rv = pPref->ClearUserPref(prefLocation.get());
+ }
+ else
+ {
+ /* If there's no default pref, look for a user pref, and only set our value in
+ * if the user pref is different than one of them.
+ */
+ nsCString userPref;
+ if (NS_SUCCEEDED(pPref->GetCharPref (prefLocation.get(), getter_Copies(userPref))))
+ {
+ if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue))
+ rv = pPref->SetCharPref (prefLocation.get(), value);
+ else
+ rv = pPref->ClearUserPref(prefLocation.get());
+ }
+ else
+ {
+ if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue))
+ rv = pPref->SetCharPref (prefLocation.get(), value);
+ }
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetStringPref");
+}
+
+static void DIR_SetLocalizedStringPref
+(const char *prefRoot, const char *prefLeaf, const char *value)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+
+ if (NS_FAILED(rv))
+ return;
+
+ nsAutoCString prefLocation(prefRoot);
+ prefLocation.Append('.');
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefSvc->GetBranch(prefLocation.get(), getter_AddRefs(prefBranch));
+ if (NS_FAILED(rv))
+ return;
+
+ nsString wvalue;
+ nsCOMPtr<nsIPrefLocalizedString> newStr(
+ do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ {
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not createInstance in DIR_SetLocalizedStringPref");
+ return;
+ }
+
+ NS_ConvertUTF8toUTF16 newValue(value);
+
+ rv = newStr->SetData(newValue.get());
+ if (NS_FAILED(rv))
+ {
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref data in DIR_SetLocalizedStringPref");
+ return;
+ }
+ nsCOMPtr<nsIPrefLocalizedString> locStr;
+ if (NS_SUCCEEDED(prefBranch->GetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(locStr))))
+ {
+ nsString data;
+ locStr->GetData(getter_Copies(data));
+
+ // Only set the pref if the data values aren't the same (i.e. don't change
+ // unnecessarily, but also, don't change in the case that its a chrome
+ // string pointing to the value we want to set the pref to).
+ if (newValue != data)
+ rv = prefBranch->SetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ newStr);
+ }
+ else {
+ // No value set, but check the default pref branch (i.e. user may have
+ // cleared the pref)
+ nsCOMPtr<nsIPrefBranch> dPB;
+ rv = prefSvc->GetDefaultBranch(prefLocation.get(),
+ getter_AddRefs(dPB));
+
+ if (NS_SUCCEEDED(dPB->GetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(locStr))))
+ {
+ // Default branch has a value
+ nsString data;
+ locStr->GetData(getter_Copies(data));
+
+ if (newValue != data)
+ // If the vales aren't the same, set the data on the main pref branch
+ rv = prefBranch->SetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ newStr);
+ else
+ // Else if they are, kill the user pref
+ rv = prefBranch->ClearUserPref(prefLeaf);
+ }
+ else
+ // No values set anywhere, so just set the pref
+ rv = prefBranch->SetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ newStr);
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetLocalizedStringPref");
+}
+
+
+static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ int32_t defaultPref;
+ nsAutoCString prefLocation(prefRoot);
+
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+
+ if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &defaultPref)))
+ {
+ /* solve the problem where reordering user prefs must override default prefs */
+ rv = pPref->SetIntPref(prefLocation.get(), value);
+ }
+ else
+ {
+ int32_t userPref;
+ if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &userPref)))
+ {
+ if (value != defaultValue)
+ rv = pPref->SetIntPref(prefLocation.get(), value);
+ else
+ rv = pPref->ClearUserPref(prefLocation.get());
+ }
+ else
+ {
+ if (value != defaultValue)
+ rv = pPref->SetIntPref(prefLocation.get(), value);
+ }
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetIntPref");
+}
+
+void DIR_SavePrefsForOneServer(DIR_Server *server)
+{
+ if (!server)
+ return;
+
+ char *prefstring;
+
+ if (server->prefName == nullptr)
+ server->prefName = dir_CreateServerPrefName(server);
+ prefstring = server->prefName;
+
+ server->savingServer = true;
+
+ DIR_SetIntPref (prefstring, "position", server->position, kDefaultPosition);
+
+ // Only save the non-default address book name
+ DIR_SetLocalizedStringPref(prefstring, "description", server->description);
+
+ DIR_SetStringPref(prefstring, "filename", server->fileName, "");
+ DIR_SetIntPref(prefstring, "dirType", server->dirType, LDAPDirectory);
+
+ if (server->dirType != PABDirectory)
+ DIR_SetStringPref(prefstring, "uri", server->uri, "");
+
+ server->savingServer = false;
+}
+
+static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList)
+{
+ if (wholeList)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("DIR_SaveServerPreferences: Failed to get the pref service\n");
+ return;
+ }
+
+ int32_t i;
+ int32_t count = wholeList->Length();
+ DIR_Server *server;
+
+ for (i = 0; i < count; i++)
+ {
+ server = wholeList->ElementAt(i);
+ if (server)
+ DIR_SavePrefsForOneServer(server);
+ }
+ pPref->SetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", dir_UserId);
+ }
+}
diff --git a/mailnews/addrbook/src/nsDirPrefs.h b/mailnews/addrbook/src/nsDirPrefs.h
new file mode 100644
index 000000000..d27a4fcb5
--- /dev/null
+++ b/mailnews/addrbook/src/nsDirPrefs.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _NSDIRPREFS_H_
+#define _NSDIRPREFS_H_
+
+#include "nsTArray.h"
+
+//
+// XXX nsDirPrefs is being greatly reduced if not removed altogether. Directory
+// Prefs etc. should be handled via their appropriate nsAb*Directory classes.
+//
+
+#define kPreviousListVersion 2
+#define kCurrentListVersion 3
+#define PREF_LDAP_GLOBAL_TREE_NAME "ldap_2"
+#define PREF_LDAP_VERSION_NAME "ldap_2.version"
+#define PREF_LDAP_SERVER_TREE_NAME "ldap_2.servers"
+
+#define kMainLdapAddressBook "ldap.mab" /* v3 main ldap address book file */
+
+/* DIR_Server.dirType */
+typedef enum
+{
+ LDAPDirectory,
+ HTMLDirectory,
+ PABDirectory,
+ MAPIDirectory,
+ FixedQueryLDAPDirectory = 777
+} DirectoryType;
+
+typedef enum
+{
+ idNone = 0, /* Special value */
+ idPrefName,
+ idPosition,
+ idDescription,
+ idFileName,
+ idUri,
+ idType
+} DIR_PrefId;
+
+#define DIR_Server_typedef 1 /* this quiets a redeclare warning in libaddr */
+
+typedef struct DIR_Server
+{
+ /* Housekeeping fields */
+ char *prefName; /* preference name, this server's subtree */
+ int32_t position; /* relative position in server list */
+
+ /* General purpose fields */
+ char *description; /* human readable name */
+ char *fileName; /* XP path name of local DB */
+ DirectoryType dirType;
+ char *uri; // URI of the address book
+
+ // Set whilst saving the server to avoid updating it again
+ bool savingServer;
+} DIR_Server;
+
+/* We are developing a new model for managing DIR_Servers. In the 4.0x world, the FEs managed each list.
+ Calls to FE_GetDirServer caused the FEs to manage and return the DIR_Server list. In our new view of the
+ world, the back end does most of the list management so we are going to have the back end create and
+ manage the list. Replace calls to FE_GetDirServers() with DIR_GetDirServers(). */
+
+nsTArray<DIR_Server*>* DIR_GetDirectories();
+DIR_Server* DIR_GetServerFromList(const char* prefName);
+nsresult DIR_ShutDown(void); /* FEs should call this when the app is shutting down. It frees all DIR_Servers regardless of ref count values! */
+
+nsresult DIR_AddNewAddressBook(const nsAString &dirName,
+ const nsACString &fileName,
+ const nsACString &uri,
+ DirectoryType dirType,
+ const nsACString &prefName,
+ DIR_Server** pServer);
+nsresult DIR_ContainsServer(DIR_Server* pServer, bool *hasDir);
+
+nsresult DIR_DeleteServerFromList (DIR_Server *);
+
+void DIR_SavePrefsForOneServer(DIR_Server *server);
+
+void DIR_SetServerFileName(DIR_Server* pServer);
+
+#endif /* dirprefs.h */
diff --git a/mailnews/addrbook/src/nsMapiAddressBook.cpp b/mailnews/addrbook/src/nsMapiAddressBook.cpp
new file mode 100644
index 000000000..80d737fb2
--- /dev/null
+++ b/mailnews/addrbook/src/nsMapiAddressBook.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMapiAddressBook.h"
+
+#include "mozilla/Logging.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gMapiAddressBookLog
+ = PR_NewLogModule("nsMapiAddressBookLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(gMapiAddressBookLog, mozilla::LogLevel::Debug, args)
+
+using namespace mozilla;
+
+HMODULE nsMapiAddressBook::mLibrary = NULL ;
+int32_t nsMapiAddressBook::mLibUsage = 0 ;
+LPMAPIINITIALIZE nsMapiAddressBook::mMAPIInitialize = NULL ;
+LPMAPIUNINITIALIZE nsMapiAddressBook::mMAPIUninitialize = NULL ;
+LPMAPIALLOCATEBUFFER nsMapiAddressBook::mMAPIAllocateBuffer = NULL ;
+LPMAPIFREEBUFFER nsMapiAddressBook::mMAPIFreeBuffer = NULL ;
+LPMAPILOGONEX nsMapiAddressBook::mMAPILogonEx = NULL ;
+
+BOOL nsMapiAddressBook::mInitialized = FALSE ;
+BOOL nsMapiAddressBook::mLogonDone = FALSE ;
+LPMAPISESSION nsMapiAddressBook::mRootSession = NULL ;
+LPADRBOOK nsMapiAddressBook::mRootBook = NULL ;
+
+BOOL nsMapiAddressBook::LoadMapiLibrary(void)
+{
+ if (mLibrary) { ++ mLibUsage ; return TRUE ; }
+ HMODULE libraryHandle = LoadLibrary("MAPI32.DLL") ;
+
+ if (!libraryHandle) { return FALSE ; }
+ FARPROC entryPoint = GetProcAddress(libraryHandle, "MAPIGetNetscapeVersion") ;
+
+ if (entryPoint) {
+ FreeLibrary(libraryHandle) ;
+ libraryHandle = LoadLibrary("MAPI32BAK.DLL") ;
+ if (!libraryHandle) { return FALSE ; }
+ }
+ mLibrary = libraryHandle ;
+ ++ mLibUsage ;
+ mMAPIInitialize = reinterpret_cast<LPMAPIINITIALIZE>(GetProcAddress(mLibrary, "MAPIInitialize")) ;
+ if (!mMAPIInitialize) { return FALSE ; }
+ mMAPIUninitialize = reinterpret_cast<LPMAPIUNINITIALIZE>(GetProcAddress(mLibrary, "MAPIUninitialize")) ;
+ if (!mMAPIUninitialize) { return FALSE ; }
+ mMAPIAllocateBuffer = reinterpret_cast<LPMAPIALLOCATEBUFFER>(GetProcAddress(mLibrary, "MAPIAllocateBuffer")) ;
+ if (!mMAPIAllocateBuffer) { return FALSE ; }
+ mMAPIFreeBuffer = reinterpret_cast<LPMAPIFREEBUFFER>(GetProcAddress(mLibrary, "MAPIFreeBuffer")) ;
+ if (!mMAPIFreeBuffer) { return FALSE ; }
+ mMAPILogonEx = reinterpret_cast<LPMAPILOGONEX>(GetProcAddress(mLibrary, "MAPILogonEx")) ;
+ if (!mMAPILogonEx) { return FALSE ; }
+ MAPIINIT_0 mapiInit = { MAPI_INIT_VERSION, MAPI_MULTITHREAD_NOTIFICATIONS } ;
+ HRESULT retCode = mMAPIInitialize(&mapiInit) ;
+
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot initialize MAPI %08x.\n", retCode)) ; return FALSE ;
+ }
+ mInitialized = TRUE ;
+ retCode = mMAPILogonEx(0, NULL, NULL,
+ MAPI_NO_MAIL |
+ MAPI_USE_DEFAULT |
+ MAPI_EXTENDED |
+ MAPI_NEW_SESSION,
+ &mRootSession) ;
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot logon to MAPI %08x.\n", retCode)) ; return FALSE ;
+ }
+ mLogonDone = TRUE ;
+ retCode = mRootSession->OpenAddressBook(0, NULL, 0, &mRootBook) ;
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot open MAPI address book %08x.\n", retCode)) ;
+ }
+ return HR_SUCCEEDED(retCode) ;
+}
+
+void nsMapiAddressBook::FreeMapiLibrary(void)
+{
+ if (mLibrary) {
+ if (-- mLibUsage == 0) {
+ {
+ if (mRootBook) { mRootBook->Release() ; }
+ if (mRootSession) {
+ if (mLogonDone) {
+ mRootSession->Logoff(NULL, 0, 0) ;
+ mLogonDone = FALSE ;
+ }
+ mRootSession->Release() ;
+ }
+ if (mInitialized) {
+ mMAPIUninitialize() ;
+ mInitialized = FALSE ;
+ }
+ }
+ FreeLibrary(mLibrary) ;
+ mLibrary = NULL ;
+ }
+ }
+}
+
+nsMapiAddressBook::nsMapiAddressBook(void)
+: nsAbWinHelper()
+{
+ BOOL result = Initialize() ;
+
+ NS_ASSERTION(result == TRUE, "Couldn't initialize Mapi Helper") ;
+ MOZ_COUNT_CTOR(nsMapiAddressBook) ;
+}
+
+nsMapiAddressBook::~nsMapiAddressBook(void)
+{
+ MutexAutoLock guard(*mMutex) ;
+
+ FreeMapiLibrary() ;
+ MOZ_COUNT_DTOR(nsMapiAddressBook) ;
+}
+
+BOOL nsMapiAddressBook::Initialize(void)
+{
+ if (mAddressBook) { return TRUE ; }
+ MutexAutoLock guard(*mMutex) ;
+
+ if (!LoadMapiLibrary()) {
+ PRINTF(("Cannot load library.\n")) ;
+ return FALSE ;
+ }
+ mAddressBook = mRootBook ;
+ return TRUE ;
+}
+
+void nsMapiAddressBook::AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer)
+{
+ mMAPIAllocateBuffer(aByteCount, aBuffer) ;
+}
+
+void nsMapiAddressBook::FreeBuffer(LPVOID aBuffer)
+{
+ mMAPIFreeBuffer(aBuffer) ;
+}
+
+
+
+
+
diff --git a/mailnews/addrbook/src/nsMapiAddressBook.h b/mailnews/addrbook/src/nsMapiAddressBook.h
new file mode 100644
index 000000000..ed19b01be
--- /dev/null
+++ b/mailnews/addrbook/src/nsMapiAddressBook.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsMapiAddressBook_h___
+#define nsMapiAddressBook_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbWinHelper.h"
+
+class nsMapiAddressBook : public nsAbWinHelper
+{
+public :
+ nsMapiAddressBook(void) ;
+ virtual ~nsMapiAddressBook(void) ;
+
+protected :
+ // Class members to handle the library/entry points
+ static HMODULE mLibrary ;
+ static int32_t mLibUsage ;
+ static LPMAPIINITIALIZE mMAPIInitialize ;
+ static LPMAPIUNINITIALIZE mMAPIUninitialize ;
+ static LPMAPIALLOCATEBUFFER mMAPIAllocateBuffer ;
+ static LPMAPIFREEBUFFER mMAPIFreeBuffer ;
+ static LPMAPILOGONEX mMAPILogonEx ;
+ // Shared session and address book used by all instances.
+ // For reasons best left unknown, MAPI doesn't seem to like
+ // having different threads playing with supposedly different
+ // sessions and address books. They ll end up fighting over
+ // the same resources, with hangups and GPF resulting. Not nice.
+ // So it seems that if everybody (as long as some client is
+ // still alive) is using the same sessions and address books,
+ // MAPI feels better. And who are we to get in the way of MAPI
+ // happiness? Thus the following class members:
+ static BOOL mInitialized ;
+ static BOOL mLogonDone ;
+ static LPMAPISESSION mRootSession ;
+ static LPADRBOOK mRootBook ;
+
+ // Load the MAPI environment
+ BOOL Initialize(void) ;
+ // Allocation of a buffer for transmission to interfaces
+ virtual void AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) override;
+ // Destruction of a buffer provided by the interfaces
+ virtual void FreeBuffer(LPVOID aBuffer) override;
+ // Library management
+ static BOOL LoadMapiLibrary(void) ;
+ static void FreeMapiLibrary(void) ;
+
+private :
+} ;
+
+#endif // nsMapiAddressBook_h___
+
diff --git a/mailnews/addrbook/src/nsMsgVCardService.cpp b/mailnews/addrbook/src/nsMsgVCardService.cpp
new file mode 100644
index 000000000..91f6f522e
--- /dev/null
+++ b/mailnews/addrbook/src/nsMsgVCardService.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgVCardService.h"
+#include "nsVCard.h"
+#include "prmem.h"
+#include "plstr.h"
+
+NS_IMPL_ISUPPORTS(nsMsgVCardService, nsIMsgVCardService)
+
+nsMsgVCardService::nsMsgVCardService()
+{
+}
+
+nsMsgVCardService::~nsMsgVCardService()
+{
+}
+
+NS_IMETHODIMP_(void) nsMsgVCardService::CleanVObject(VObject * o)
+{
+ cleanVObject(o);
+}
+
+NS_IMETHODIMP_(VObject *) nsMsgVCardService::NextVObjectInList(VObject * o)
+{
+ return nextVObjectInList(o);
+}
+
+NS_IMETHODIMP_(VObject *) nsMsgVCardService::Parse_MIME(const char *input, uint32_t len)
+{
+ return parse_MIME(input, (unsigned long)len);
+}
+
+NS_IMETHODIMP_(char *) nsMsgVCardService::FakeCString(VObject * o)
+{
+ return fakeCString(vObjectUStringZValue(o));
+}
+
+NS_IMETHODIMP_(VObject *) nsMsgVCardService::IsAPropertyOf(VObject * o, const char *id)
+{
+ return isAPropertyOf(o,id);
+}
+
+NS_IMETHODIMP_(char *) nsMsgVCardService::WriteMemoryVObjects(const char *s, int32_t *len, VObject * list, bool expandSpaces)
+{
+ return writeMemoryVObjects((char *)s, len, list, expandSpaces);
+}
+
+NS_IMETHODIMP_(VObject *) nsMsgVCardService::NextVObject(VObjectIterator * i)
+{
+ return nextVObject(i);
+}
+
+NS_IMETHODIMP_(void) nsMsgVCardService::InitPropIterator(VObjectIterator * i, VObject * o)
+{
+ initPropIterator(i,o);
+}
+
+NS_IMETHODIMP_(int32_t) nsMsgVCardService::MoreIteration(VObjectIterator * i)
+{
+ return ((int32_t)moreIteration(i));
+}
+
+NS_IMETHODIMP_(const char *) nsMsgVCardService::VObjectName(VObject * o)
+{
+ return vObjectName(o);
+}
+
+NS_IMETHODIMP_(char *) nsMsgVCardService::VObjectAnyValue(VObject * o)
+{
+ char *retval = (char *)PR_MALLOC(strlen((char *)vObjectAnyValue(o)) + 1);
+ if (retval)
+ PL_strcpy(retval, (char *) vObjectAnyValue(o));
+ return retval;
+}
diff --git a/mailnews/addrbook/src/nsMsgVCardService.h b/mailnews/addrbook/src/nsMsgVCardService.h
new file mode 100644
index 000000000..7e7da8766
--- /dev/null
+++ b/mailnews/addrbook/src/nsMsgVCardService.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgVCardService_h___
+#define nsMsgVCardService_h___
+
+#include "nsIMsgVCardService.h"
+#include "nsISupports.h"
+
+class nsMsgVCardService : public nsIMsgVCardService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGVCARDSERVICE
+
+ nsMsgVCardService();
+
+private:
+ virtual ~nsMsgVCardService();
+};
+
+#endif /* nsMsgVCardService_h___ */
diff --git a/mailnews/addrbook/src/nsVCard.cpp b/mailnews/addrbook/src/nsVCard.cpp
new file mode 100644
index 000000000..b8c2455b2
--- /dev/null
+++ b/mailnews/addrbook/src/nsVCard.cpp
@@ -0,0 +1,1571 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/***************************************************************************
+(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+
+For purposes of this license notice, the term Licensors shall mean,
+collectively, Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+The term Licensor shall mean any of the Licensors.
+
+Subject to acceptance of the following conditions, permission is hereby
+granted by Licensors without the need for written agreement and without
+license or royalty fees, to use, copy, modify and distribute this
+software for any purpose.
+
+The above copyright notice and the following four paragraphs must be
+reproduced in all copies of this software and any software including
+this software.
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE
+ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR
+MODIFICATIONS.
+
+IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT,
+INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT
+OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.
+
+The software is provided with RESTRICTED RIGHTS. Use, duplication, or
+disclosure by the government are subject to restrictions set forth in
+DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable.
+
+***************************************************************************/
+
+/*
+ * src: vcc.c
+ * doc: Parser for vCard and vCalendar. Note that this code is
+ * generated by a yacc parser generator. Generally it should not
+ * be edited by hand. The real source is vcc.y. The #line directives
+ * can be commented out here to make it easier to trace through
+ * in a debugger. However, if a bug is found it should
+ *
+ * the vcc.y that _this_ vcc.c comes from is lost.
+ * I couldn't find it in the 4.x tree
+ * I bet we took it from IMC's original SDK, but the SDK has been taken down.
+ * see http://www.imc.org/imc-vcard/mail-archive/msg00460.html
+ *
+ * for what it's worth, see
+ * http://softwarestudio.org/libical/
+ * http://lxr.mozilla.org/mozilla/source/other-licenses/libical/src/libicalvcal/vcc.y
+ * http://lxr.mozilla.org/mozilla/source/other-licenses/libical/src/libicalvcal/vcc.c
+ */
+#include "nsVCard.h"
+#include "nsVCardObj.h"
+#include "prprf.h"
+#include "nscore.h"
+#include <string.h>
+#include <ctype.h>
+
+#ifndef lint
+char yysccsid[] = "@(#)yaccpar 1.4 (Berkeley) 02/25/90";
+#endif
+/*#line 2 "vcc.y" */
+
+/* debugging utilities */
+#define DBG_(x)
+
+#ifndef _NO_LINE_FOLDING
+#define _SUPPORT_LINE_FOLDING
+#endif
+
+/**** External Functions ****/
+
+/* assign local name to parser variables and functions so that
+ we can use more than one yacc based parser.
+*/
+
+#define yyparse mime_parse
+#define yylex mime_lex
+#define yyerror mime_error
+#define yychar mime_char
+/* #define p_yyval p_mime_val */
+#undef yyval
+#define yyval mime_yyval
+/* #define p_yylval p_mime_lval */
+#undef yylval
+#define yylval mime_yylval
+#define yydebug mime_debug
+#define yynerrs mime_nerrs
+#define yyerrflag mime_errflag
+#define yyss mime_ss
+#define yyssp mime_ssp
+#define yyvs mime_vs
+#define yyvsp mime_vsp
+#define yylhs mime_lhs
+#define yylen mime_len
+#define yydefred mime_defred
+#define yydgoto mime_dgoto
+#define yysindex mime_sindex
+#define yyrindex mime_rindex
+#define yygindex mime_gindex
+#define yytable mime_table
+#define yycheck mime_check
+#define yyname mime_name
+#define yyrule mime_rule
+#define YYPREFIX "mime_"
+
+#include "prmem.h"
+#include "plstr.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+/**** Types, Constants ****/
+
+#define YYDEBUG 0 /* 1 to compile in some debugging code */
+#define PR_MAXTOKEN 256 /* maximum token (line) length */
+#define YYSTACKSIZE 50 /* ~unref ?*/
+#define PR_MAXLEVEL 10 /* max # of nested objects parseable */
+ /* (includes outermost) */
+
+
+/**** Global Variables ****/
+int mime_lineNum, mime_numErrors; /* yyerror() can use these */
+static VObject* vObjList;
+static VObject *curProp;
+static VObject *curObj;
+static VObject* ObjStack[PR_MAXLEVEL];
+static int ObjStackTop;
+
+
+/* A helpful utility for the rest of the app. */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ extern void yyerror(const char *s);
+ extern char** fieldedProp;
+
+#ifdef __cplusplus
+ }
+#endif
+
+int yyparse();
+
+enum LexMode {
+ L_NORMAL,
+ L_VCARD,
+ L_VCAL,
+ L_VEVENT,
+ L_VTODO,
+ L_VALUES,
+ L_BASE64,
+ L_QUOTED_PRINTABLE
+ };
+
+/**** Private Forward Declarations ****/
+static int pushVObject(const char *prop);
+static VObject* popVObject();
+static int lexGeta();
+static int lexGetc_();
+static int lexGetc();
+static void lexSkipLookahead();
+static int lexLookahead();
+static void lexSkipWhite();
+static void lexClearToken();
+static char * lexStr();
+static char * lexGetDataFromBase64();
+static char * lexGetQuotedPrintable();
+static char * lexGet1Value();
+static char * lexGetWord();
+static void finiLex();
+
+static VObject* parse_MIMEHelper();
+
+/*static char* lexDataFromBase64();*/
+static void lexPopMode(int top);
+static int lexWithinMode(enum LexMode mode);
+static void lexPushMode(enum LexMode mode);
+static void enterProps(const char *s);
+static void enterAttr(const char *s1, const char *s2);
+static void enterValues(const char *value);
+
+/*#line 250 "vcc.y" */
+typedef union {
+ char *str;
+ VObject *vobj;
+ } YYSTYPE;
+/*#line 253 "y_tab.c"*/
+#define EQ 257
+#define COLON 258
+#define DOT 259
+#define SEMICOLON 260
+#define SPACE 261
+#define HTAB 262
+#define LINESEP 263
+#define NEWLINE 264
+#define BEGIN_VCARD 265
+#define END_VCARD 266
+#define BEGIN_VCAL 267
+#define END_VCAL 268
+#define BEGIN_VEVENT 269
+#define END_VEVENT 270
+#define BEGIN_VTODO 271
+#define END_VTODO 272
+#define ID 273
+#define STRING 274
+#define YYERRCODE 256
+short yylhs[] = { -1,
+ 0, 7, 6, 6, 5, 5, 9, 3, 10, 3,
+ 8, 8, 14, 11, 11, 16, 12, 12, 15, 15,
+ 17, 18, 18, 1, 19, 13, 13, 2, 2, 21,
+ 4, 22, 4, 20, 20, 23, 23, 23, 26, 24,
+ 27, 24, 28, 25, 29, 25,
+};
+short yylen[] = { 2,
+ 1, 0, 3, 1, 1, 1, 0, 4, 0, 3,
+ 2, 1, 0, 5, 1, 0, 3, 1, 2, 1,
+ 2, 1, 3, 1, 0, 4, 1, 1, 0, 0,
+ 4, 0, 3, 2, 1, 1, 1, 1, 0, 4,
+ 0, 3, 0, 4, 0, 3,
+};
+short yydefred[] = { 0,
+ 0, 0, 0, 5, 6, 0, 1, 0, 0, 0,
+ 0, 0, 15, 24, 0, 0, 0, 0, 10, 0,
+ 0, 38, 0, 0, 36, 37, 33, 3, 0, 8,
+ 11, 13, 0, 0, 0, 0, 31, 34, 0, 17,
+ 0, 0, 0, 42, 0, 46, 0, 21, 19, 28,
+ 0, 0, 40, 44, 0, 25, 14, 23, 0, 26,
+};
+short yydgoto[] = { 3,
+ 15, 51, 4, 5, 6, 7, 12, 22, 8, 9,
+ 17, 18, 52, 42, 40, 29, 41, 48, 59, 23,
+ 10, 11, 24, 25, 26, 33, 34, 35, 36,
+};
+short yysindex[] = { -227,
+ 0, 0, 0, 0, 0, 0, 0, -249, -262, -253,
+ -258, -227, 0, 0, 0, -234, -249, -215, 0, 0,
+ 0, 0, -223, -253, 0, 0, 0, 0, -247, 0,
+ 0, 0, -249, -222, -249, -225, 0, 0, -224, 0,
+ -247, -221, -220, 0, -218, 0, -206, 0, 0, 0,
+ -208, -207, 0, 0, -224, 0, 0, 0, -221, 0,
+};
+short yyrindex[] = { 0,
+ -245, -254, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, -219, 0, -235, 0, 0, -244,
+ -250, 0, 0, -213, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ -201, -255, 0, 0, 0, 0, -216, 0, 0, 0,
+ -205, 0, 0, 0, 0, 0, 0, 0, -255, 0,
+};
+short yygindex[] = { 0,
+ -9, 0, 0, 0, 0, 47, 0, -8, 0, 0,
+ 0, 0, 2, 0, 19, 0, 0, 0, 0, 38,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+#define YYTABLESIZE 268
+short yytable[] = { 16,
+ 4, 30, 13, 19, 29, 43, 13, 29, 31, 27,
+ 7, 39, 39, 32, 30, 20, 30, 21, 30, 14,
+ 9, 45, 43, 14, 43, 41, 45, 7, 39, 47,
+ 12, 30, 12, 12, 12, 12, 12, 1, 18, 2,
+ 16, 22, 32, 22, 37, 58, 46, 44, 14, 53,
+ 55, 56, 50, 54, 35, 57, 20, 27, 28, 49,
+ 60, 38, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 2,
+};
+short yycheck[] = { 8,
+ 0, 256, 256, 266, 260, 256, 256, 263, 17, 268,
+ 256, 256, 260, 268, 269, 269, 271, 271, 273, 273,
+ 266, 272, 273, 273, 33, 270, 35, 273, 273, 39,
+ 266, 266, 268, 269, 270, 271, 272, 265, 258, 267,
+ 260, 258, 258, 260, 268, 55, 272, 270, 273, 270,
+ 257, 260, 274, 272, 268, 263, 258, 263, 12, 41,
+ 59, 24, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 265, -1, 267,
+};
+#define YYFINAL 3
+#ifndef YYDEBUG
+#define YYDEBUG 0
+#endif
+#define YYPR_MAXTOKEN 274
+#if YYDEBUG
+char *yyname[] = {
+"end-of-file",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"EQ","COLON","DOT","SEMICOLON",
+"SPACE","HTAB","LINESEP","NEWLINE","BEGIN_VCARD","END_VCARD","BEGIN_VCAL",
+"END_VCAL","BEGIN_VEVENT","END_VEVENT","BEGIN_VTODO","END_VTODO","ID","STRING",
+};
+char *yyrule[] = {
+"$accept : mime",
+"mime : vobjects",
+"$$1 :",
+"vobjects : vobject $$1 vobjects",
+"vobjects : vobject",
+"vobject : vcard",
+"vobject : vcal",
+"$$2 :",
+"vcard : BEGIN_VCARD $$2 items END_VCARD",
+"$$3 :",
+"vcard : BEGIN_VCARD $$3 END_VCARD",
+"items : item items",
+"items : item",
+"$$4 :",
+"item : prop COLON $$4 values LINESEP",
+"item : error",
+"$$5 :",
+"prop : name $$5 attr_params",
+"prop : name",
+"attr_params : attr_param attr_params",
+"attr_params : attr_param",
+"attr_param : SEMICOLON attr",
+"attr : name",
+"attr : name EQ name",
+"name : ID",
+"$$6 :",
+"values : value SEMICOLON $$6 values",
+"values : value",
+"value : STRING",
+"value :",
+"$$7 :",
+"vcal : BEGIN_VCAL $$7 calitems END_VCAL",
+"$$8 :",
+"vcal : BEGIN_VCAL $$8 END_VCAL",
+"calitems : calitem calitems",
+"calitems : calitem",
+"calitem : eventitem",
+"calitem : todoitem",
+"calitem : items",
+"$$9 :",
+"eventitem : BEGIN_VEVENT $$9 items END_VEVENT",
+"$$10 :",
+"eventitem : BEGIN_VEVENT $$10 END_VEVENT",
+"$$11 :",
+"todoitem : BEGIN_VTODO $$11 items END_VTODO",
+"$$12 :",
+"todoitem : BEGIN_VTODO $$12 END_VTODO",
+};
+#endif
+#define yyclearin (yychar=(-1))
+#define yyerrok (yyerrflag=0)
+#ifndef YYSTACKSIZE
+#ifdef YYPR_MAXDEPTH
+#define YYSTACKSIZE YYPR_MAXDEPTH
+#else
+#define YYSTACKSIZE 300
+#endif
+#endif
+int yydebug;
+int yynerrs;
+int yyerrflag;
+int yychar;
+short *yyssp;
+YYSTYPE *yyvsp;
+YYSTYPE yyval;
+YYSTYPE yylval;
+#define yystacksize YYSTACKSIZE
+short yyss[YYSTACKSIZE];
+YYSTYPE yyvs[YYSTACKSIZE];
+/*#line 444 "vcc.y"*/
+/******************************************************************************/
+static int pushVObject(const char *prop)
+ {
+ VObject *newObj;
+ if (ObjStackTop == PR_MAXLEVEL)
+ return FALSE;
+
+ ObjStack[++ObjStackTop] = curObj;
+
+ if (curObj) {
+ newObj = addProp(curObj,prop);
+ curObj = newObj;
+ }
+ else
+ curObj = newVObject(prop);
+
+ return TRUE;
+ }
+
+
+/******************************************************************************/
+/* This pops the recently built vCard off the stack and returns it. */
+static VObject* popVObject()
+ {
+ VObject *oldObj;
+ if (ObjStackTop < 0) {
+ yyerror("pop on empty Object Stack\n");
+ return 0;
+ }
+ oldObj = curObj;
+ curObj = ObjStack[ObjStackTop--];
+
+ return oldObj;
+ }
+
+extern "C" void deleteString(char *p);
+
+static void enterValues(const char *value)
+ {
+ if (fieldedProp && *fieldedProp) {
+ if (value) {
+ addPropValue(curProp,*fieldedProp,value);
+ }
+ /* else this field is empty, advance to next field */
+ fieldedProp++;
+ }
+ else {
+ if (value) {
+ setVObjectUStringZValue_(curProp,fakeUnicode(value,0));
+ }
+ }
+ deleteString((char *)value);
+ }
+
+static void enterProps(const char *s)
+ {
+ curProp = addGroup(curObj,s);
+ deleteString((char *)s);
+ }
+
+static void enterAttr(const char *s1, const char *s2)
+{
+ const char *p1, *p2 = nullptr;
+ p1 = lookupProp_(s1);
+ if (s2) {
+ VObject *a;
+ p2 = lookupProp_(s2);
+ a = addProp(curProp,p1);
+ setVObjectStringZValue(a,p2);
+ }
+ else
+ addProp(curProp,p1);
+ if (PL_strcasecmp(p1,VCBase64Prop) == 0 || (s2 && PL_strcasecmp(p2,VCBase64Prop)==0))
+ lexPushMode(L_BASE64);
+ else if (PL_strcasecmp(p1,VCQuotedPrintableProp) == 0
+ || (s2 && PL_strcasecmp(p2,VCQuotedPrintableProp)==0))
+ lexPushMode(L_QUOTED_PRINTABLE);
+ deleteString((char *)s1); deleteString((char *)s2);
+}
+
+
+#define PR_MAX_LEX_LOOKAHEAD_0 32
+#define PR_MAX_LEX_LOOKAHEAD 64
+#define PR_MAX_LEX_MODE_STACK_SIZE 10
+#define LEXMODE() (lexBuf.lexModeStack[lexBuf.lexModeStackTop])
+
+struct LexBuf {
+ /* input */
+ char *inputString;
+ unsigned long curPos;
+ unsigned long inputLen;
+ /* lookahead buffer */
+ /* -- lookahead buffer is short instead of char so that EOF
+ / can be represented correctly.
+ */
+ unsigned long len;
+ short buf[PR_MAX_LEX_LOOKAHEAD];
+ unsigned long getPtr;
+ /* context stack */
+ unsigned long lexModeStackTop;
+ enum LexMode lexModeStack[PR_MAX_LEX_MODE_STACK_SIZE];
+ /* token buffer */
+ unsigned long maxToken;
+ char *strs;
+ unsigned long strsLen;
+ } lexBuf;
+
+static void lexPushMode(enum LexMode mode)
+ {
+ if (lexBuf.lexModeStackTop == (PR_MAX_LEX_MODE_STACK_SIZE-1))
+ yyerror("lexical context stack overflow");
+ else {
+ lexBuf.lexModeStack[++lexBuf.lexModeStackTop] = mode;
+ }
+ }
+
+static void lexPopMode(int top)
+ {
+ /* special case of pop for ease of error recovery -- this
+ version will never underflow */
+ if (top)
+ lexBuf.lexModeStackTop = 0;
+ else
+ if (lexBuf.lexModeStackTop > 0) lexBuf.lexModeStackTop--;
+ }
+
+static int lexWithinMode(enum LexMode mode) {
+ unsigned long i;
+ for (i=0;i<lexBuf.lexModeStackTop;i++)
+ if (mode == lexBuf.lexModeStack[i]) return 1;
+ return 0;
+ }
+
+static int lexGetc_()
+{
+ /* get next char from input, no buffering. */
+ if (lexBuf.curPos == lexBuf.inputLen)
+ return EOF;
+ else if (lexBuf.inputString)
+ return *(lexBuf.inputString + lexBuf.curPos++);
+
+ return -1;
+}
+
+static int lexGeta()
+ {
+ ++lexBuf.len;
+ return (lexBuf.buf[lexBuf.getPtr] = lexGetc_());
+ }
+
+static int lexGeta_(int i)
+ {
+ ++lexBuf.len;
+ return (lexBuf.buf[(lexBuf.getPtr+i)%PR_MAX_LEX_LOOKAHEAD] = lexGetc_());
+ }
+
+static void lexSkipLookahead() {
+ if (lexBuf.len > 0 && lexBuf.buf[lexBuf.getPtr]!=EOF) {
+ /* don't skip EOF. */
+ lexBuf.getPtr = (lexBuf.getPtr + 1) % PR_MAX_LEX_LOOKAHEAD;
+ lexBuf.len--;
+ }
+ }
+
+static int lexLookahead() {
+ int c = (lexBuf.len)?
+ lexBuf.buf[lexBuf.getPtr]:
+ lexGeta();
+ /* do the \r\n -> \n or \r -> \n translation here */
+ if (c == '\r') {
+ int a = (lexBuf.len>1)?
+ lexBuf.buf[(lexBuf.getPtr+1)%PR_MAX_LEX_LOOKAHEAD]:
+ lexGeta_(1);
+ if (a == '\n') {
+ lexSkipLookahead();
+ }
+ lexBuf.buf[lexBuf.getPtr] = c = '\n';
+ }
+ else if (c == '\n') {
+ int a = (lexBuf.len>1)?
+ lexBuf.buf[lexBuf.getPtr+1]:
+ lexGeta_(1);
+ if (a == '\r') {
+ lexSkipLookahead();
+ }
+ lexBuf.buf[lexBuf.getPtr] = '\n';
+ }
+ return c;
+ }
+
+static int lexGetc() {
+ int c = lexLookahead();
+ if (lexBuf.len > 0 && lexBuf.buf[lexBuf.getPtr]!=EOF) {
+ /* EOF will remain in lookahead buffer */
+ lexBuf.getPtr = (lexBuf.getPtr + 1) % PR_MAX_LEX_LOOKAHEAD;
+ lexBuf.len--;
+ }
+ return c;
+ }
+
+static void lexSkipLookaheadWord() {
+ if (lexBuf.strsLen <= lexBuf.len) {
+ lexBuf.len -= lexBuf.strsLen;
+ lexBuf.getPtr = (lexBuf.getPtr + lexBuf.strsLen) % PR_MAX_LEX_LOOKAHEAD;
+ }
+ }
+
+static void lexClearToken()
+ {
+ lexBuf.strsLen = 0;
+ }
+
+static void lexAppendc(int c)
+ {
+ lexBuf.strs[lexBuf.strsLen] = c;
+ /* append up to zero termination */
+ if (c == 0) return;
+ lexBuf.strsLen++;
+ if (lexBuf.strsLen >= lexBuf.maxToken) {
+ /* double the token string size */
+ lexBuf.maxToken <<= 1;
+ lexBuf.strs = (char*) PR_Realloc(lexBuf.strs,lexBuf.maxToken);
+ }
+ }
+
+static char* lexStr() {
+ return dupStr(lexBuf.strs,lexBuf.strsLen+1);
+ }
+
+static void lexSkipWhite() {
+ int c = lexLookahead();
+ while (c == ' ' || c == '\t') {
+ lexSkipLookahead();
+ c = lexLookahead();
+ }
+ }
+
+static char* lexGetWord() {
+ int c;
+ lexSkipWhite();
+ lexClearToken();
+ c = lexLookahead();
+ while (c != EOF && !PL_strchr("\t\n ;:=",(char)c)) {
+ lexAppendc(c);
+ lexSkipLookahead();
+ c = lexLookahead();
+ }
+ lexAppendc(0);
+ return lexStr();
+ }
+
+#if 0
+static void lexPushLookahead(char *s, int len) {
+ int putptr;
+ if (len == 0) len = PL_strlen(s);
+ putptr = lexBuf.getPtr - len;
+ /* this function assumes that length of word to push back
+ / is not greater than PR_MAX_LEX_LOOKAHEAD.
+ */
+ if (putptr < 0) putptr += PR_MAX_LEX_LOOKAHEAD;
+ lexBuf.getPtr = putptr;
+ while (*s) {
+ lexBuf.buf[putptr] = *s++;
+ putptr = (putptr + 1) % PR_MAX_LEX_LOOKAHEAD;
+ }
+ lexBuf.len += len;
+ }
+#endif
+
+static void lexPushLookaheadc(int c) {
+ int putptr;
+ /* can't putback EOF, because it never leaves lookahead buffer */
+ if (c == EOF) return;
+ putptr = (int) lexBuf.getPtr - 1;
+ if (putptr < 0) putptr += PR_MAX_LEX_LOOKAHEAD;
+ lexBuf.getPtr = putptr;
+ lexBuf.buf[putptr] = c;
+ lexBuf.len += 1;
+ }
+
+static char* lexLookaheadWord() {
+ /* this function can lookahead word with max size of PR_MAX_LEX_LOOKAHEAD_0
+ / and thing bigger than that will stop the lookahead and return 0;
+ / leading white spaces are not recoverable.
+ */
+ int c;
+ int len = 0;
+ int curgetptr = 0;
+ lexSkipWhite();
+ lexClearToken();
+ curgetptr = (int) lexBuf.getPtr; /* remember! */
+ while (len < (PR_MAX_LEX_LOOKAHEAD_0)) {
+ c = lexGetc();
+ len++;
+ if (c == EOF || PL_strchr("\t\n ;:=", (char)c)) {
+ lexAppendc(0);
+ /* restore lookahead buf. */
+ lexBuf.len += len;
+ lexBuf.getPtr = curgetptr;
+ return lexStr();
+ }
+ else
+ lexAppendc(c);
+ }
+ lexBuf.len += len; /* char that has been moved to lookahead buffer */
+ lexBuf.getPtr = curgetptr;
+ return 0;
+ }
+
+#ifdef _SUPPORT_LINE_FOLDING
+static void handleMoreRFC822LineBreak(int c) {
+ /* support RFC 822 line break in cases like
+ * ADR: foo;
+ * morefoo;
+ * more foo;
+ */
+ if (c == ';') {
+ int a;
+ lexSkipLookahead();
+ /* skip white spaces */
+ a = lexLookahead();
+ while (a == ' ' || a == '\t') {
+ lexSkipLookahead();
+ a = lexLookahead();
+ }
+ if (a == '\n') {
+ lexSkipLookahead();
+ a = lexLookahead();
+ if (a == ' ' || a == '\t') {
+ /* continuation, throw away all the \n and spaces read so
+ * far
+ */
+ lexSkipWhite();
+ lexPushLookaheadc(';');
+ }
+ else {
+ lexPushLookaheadc('\n');
+ lexPushLookaheadc(';');
+ }
+ }
+ else {
+ lexPushLookaheadc(';');
+ }
+ }
+ }
+
+static char* lexGet1Value() {
+/* int size = 0; */
+ int c;
+ lexSkipWhite();
+ c = lexLookahead();
+ lexClearToken();
+ while (c != EOF && c != ';') {
+ if (c == '\n') {
+ int a;
+ lexSkipLookahead();
+ a = lexLookahead();
+ if (a == ' ' || a == '\t') {
+ lexAppendc(' ');
+ lexSkipLookahead();
+ }
+ else {
+ lexPushLookaheadc('\n');
+ break;
+ }
+ }
+ else if (c == '\\') {
+ int a;
+ lexSkipLookahead();
+ a = lexLookahead();
+ if (a == '\\' || a == ',' || a == ';' || a == ':') {
+ lexAppendc(a);
+ }
+ else if (a == 'n' || a == 'N') {
+ lexAppendc('\n');
+ }
+ else {
+ lexAppendc(c);
+ lexAppendc(a);
+ }
+ lexSkipLookahead();
+ }
+ else {
+ lexAppendc(c);
+ lexSkipLookahead();
+ }
+ c = lexLookahead();
+ }
+ lexAppendc(0);
+ handleMoreRFC822LineBreak(c);
+ return c==EOF?0:lexStr();
+ }
+#endif
+
+
+#ifndef _SUPPORT_LINE_FOLDING
+static char* lexGetStrUntil(char *termset) {
+ int c = lexLookahead();
+ lexClearToken();
+ while (c != EOF && !PL_strchr(termset,c)) {
+ lexAppendc(c);
+ lexSkipLookahead();
+ c = lexLookahead();
+ }
+ lexAppendc(0);
+ return c==EOF?0:lexStr();
+ }
+#endif /* ! _SUPPORT_LINE_FOLDING */
+
+static int match_begin_name(int end) {
+ char *n = lexLookaheadWord();
+ int token = ID;
+ if (n) {
+ if (!PL_strcasecmp(n,"vcard")) token = end?END_VCARD:BEGIN_VCARD;
+ else if (!PL_strcasecmp(n,"vcalendar")) token = end?END_VCAL:BEGIN_VCAL;
+ else if (!PL_strcasecmp(n,"vevent")) token = end?END_VEVENT:BEGIN_VEVENT;
+ else if (!PL_strcasecmp(n,"vtodo")) token = end?END_VTODO:BEGIN_VTODO;
+ deleteString(n);
+ return token;
+ }
+ return 0;
+ }
+
+void initLex(const char *inputstring, unsigned long inputlen)
+ {
+ /* initialize lex mode stack */
+ lexBuf.lexModeStack[lexBuf.lexModeStackTop=0] = L_NORMAL;
+
+ /* iniatialize lex buffer. */
+ lexBuf.inputString = (char*) inputstring;
+ lexBuf.inputLen = inputlen;
+ lexBuf.curPos = 0;
+
+ lexBuf.len = 0;
+ lexBuf.getPtr = 0;
+
+ lexBuf.maxToken = PR_MAXTOKEN;
+ lexBuf.strs = (char*)PR_CALLOC(PR_MAXTOKEN);
+ lexBuf.strsLen = 0;
+
+ }
+
+static void finiLex() {
+ PR_FREEIF(lexBuf.strs);
+ }
+
+
+/******************************************************************************/
+/* This parses and converts the base64 format for binary encoding into
+ * a decoded buffer (allocated with new). See RFC 1521.
+ */
+static char * lexGetDataFromBase64()
+ {
+ unsigned long bytesLen = 0, bytesMax = 0;
+ int quadIx = 0, pad = 0;
+ unsigned long trip = 0;
+ unsigned char b;
+ int c;
+ unsigned char *bytes = nullptr;
+ unsigned char *oldBytes = nullptr;
+
+ DBG_(("db: lexGetDataFromBase64\n"));
+ while (1) {
+ c = lexGetc();
+ if (c == '\n') {
+ ++mime_lineNum;
+ if (lexLookahead() == '\n') {
+ /* a '\n' character by itself means end of data */
+ break;
+ }
+ else continue; /* ignore '\n' */
+ }
+ else {
+ if ((c >= 'A') && (c <= 'Z'))
+ b = (unsigned char)(c - 'A');
+ else if ((c >= 'a') && (c <= 'z'))
+ b = (unsigned char)(c - 'a') + 26;
+ else if ((c >= '0') && (c <= '9'))
+ b = (unsigned char)(c - '0') + 52;
+ else if (c == '+')
+ b = 62;
+ else if (c == '/')
+ b = 63;
+ else if (c == '=' && (quadIx == 2 || quadIx == 3)) {
+ b = 0;
+ pad++;
+ } else if ((c == ' ') || (c == '\t')) {
+ continue;
+ } else { /* error condition */
+ if (bytes)
+ PR_Free (bytes);
+ else if (oldBytes)
+ PR_Free (oldBytes);
+ /* error recovery: skip until 2 adjacent newlines. */
+ DBG_(("db: invalid character 0x%x '%c'\n", c,c));
+ if (c != EOF) {
+ c = lexGetc();
+ while (c != EOF) {
+ if (c == '\n' && lexLookahead() == '\n') {
+ ++mime_lineNum;
+ break;
+ }
+ c = lexGetc();
+ }
+ }
+ return NULL;
+ }
+ trip = (trip << 6) | b;
+ if (++quadIx == 4) {
+ unsigned char outBytes[3];
+ int numOut;
+ int i;
+ for (i = 0; i < 3; i++) {
+ outBytes[2-i] = (unsigned char)(trip & 0xFF);
+ trip >>= 8;
+ }
+ numOut = 3 - pad;
+ if (bytesLen + numOut > bytesMax) {
+ if (!bytes) {
+ bytesMax = 1024;
+ } else {
+ bytesMax <<= 2;
+ oldBytes = bytes;
+ }
+ bytes = (unsigned char*) PR_Realloc(oldBytes, bytesMax);
+ if (!bytes) {
+ mime_error("out of memory while processing BASE64 data\n");
+ break;
+ }
+ }
+ if (bytes) {
+ memcpy(bytes + bytesLen, outBytes, numOut);
+ bytesLen += numOut;
+ }
+ trip = 0;
+ quadIx = 0;
+ pad = 0;
+ }
+ }
+ } /* while */
+ DBG_(("db: bytesLen = %d\n", bytesLen));
+ /* kludge: all this won't be necessary if we have tree form
+ representation */
+ if (bytes) {
+ setValueWithSize(curProp,bytes,(unsigned int)bytesLen);
+ PR_FREEIF(bytes);
+ }
+ else if (oldBytes) {
+ setValueWithSize(curProp,oldBytes,(unsigned int)bytesLen);
+ PR_FREEIF(oldBytes);
+ }
+ return 0;
+ }
+
+static int match_begin_end_name(int end) {
+ int token;
+ lexSkipWhite();
+ if (lexLookahead() != ':') return ID;
+ lexSkipLookahead();
+ lexSkipWhite();
+ token = match_begin_name(end);
+ if (token == ID) {
+ lexPushLookaheadc(':');
+ DBG_(("db: ID '%s'\n", yylval.str));
+ return ID;
+ }
+ else if (token != 0) {
+ lexSkipLookaheadWord();
+ deleteString(yylval.str);
+ DBG_(("db: begin/end %d\n", token));
+ return token;
+ }
+ return 0;
+ }
+
+static char* lexGetQuotedPrintable()
+ {
+ char cur;
+/* unsigned long len = 0; */
+
+ lexClearToken();
+ do {
+ cur = lexGetc();
+ switch (cur) {
+ case '=': {
+ int c = 0;
+ int next[2];
+ int tab [1];
+ int i;
+ for (i = 0; i < 2; i++) {
+ next[i] = lexGetc();
+ if (next[i] >= '0' && next[i] <= '9')
+ c = c * 16 + next[i] - '0';
+ else if (next[i] >= 'A' && next[i] <= 'F')
+ c = c * 16 + next[i] - 'A' + 10;
+ else
+ break;
+ }
+ if (i == 0) {
+ /* single '=' follow by LINESEP is continuation sign? */
+ if (next[0] == '\n') {
+ tab[0] = lexGetc();
+ if (tab[0] == '\t')
+ lexSkipWhite();
+ ++mime_lineNum;
+ }
+ else {
+ lexAppendc(cur);
+ /* lexPushLookaheadc('=');
+ goto EndString; */
+ }
+ }
+ else if (i == 1) {
+ lexPushLookaheadc(next[1]);
+ lexPushLookaheadc(next[0]);
+ lexAppendc('=');
+ } else {
+ lexAppendc(c);
+ }
+ break;
+ } /* '=' */
+ case '\n': {
+ lexPushLookaheadc('\n');
+ goto EndString;
+ }
+ case ';': {
+ lexPushLookaheadc(';');
+ goto EndString;
+ }
+ case (char)EOF:
+ break;
+ default:
+ lexAppendc(cur);
+ break;
+ } /* switch */
+ } while (cur != (char)EOF);
+
+EndString:
+ lexAppendc(0);
+ return lexStr();
+ } /* LexQuotedPrintable */
+
+static int yylex() {
+/* int token = 0; */
+
+ int lexmode = LEXMODE();
+ if (lexmode == L_VALUES) {
+ int c = lexGetc();
+ if (c == ';') {
+ DBG_(("db: SEMICOLON\n"));
+#ifdef _SUPPORT_LINE_FOLDING
+ lexPushLookaheadc(c);
+ handleMoreRFC822LineBreak(c);
+ lexSkipLookahead();
+#endif
+ return SEMICOLON;
+ }
+ else if (PL_strchr("\n",(char)c)) {
+ ++mime_lineNum;
+ /* consume all line separator(s) adjacent to each other */
+ c = lexLookahead();
+ while (PL_strchr("\n",(char)c)) {
+ lexSkipLookahead();
+ c = lexLookahead();
+ ++mime_lineNum;
+ }
+ DBG_(("db: LINESEP\n"));
+ return LINESEP;
+ }
+ else {
+ char *p = 0;
+ lexPushLookaheadc(c);
+ if (lexWithinMode(L_BASE64)) {
+ /* get each char and convert to bin on the fly... */
+ p = lexGetDataFromBase64();
+ yylval.str = p;
+ return !p && lexLookahead() == EOF ? 0 : STRING;
+ }
+ else if (lexWithinMode(L_QUOTED_PRINTABLE)) {
+ p = lexGetQuotedPrintable();
+ }
+ else {
+#ifdef _SUPPORT_LINE_FOLDING
+ p = lexGet1Value();
+#else
+ p = lexGetStrUntil(";\n");
+#endif
+ }
+ if (p && (*p || lexLookahead() != EOF)) {
+ DBG_(("db: STRING: '%s'\n", p));
+ yylval.str = p;
+ return STRING;
+ }
+ else return 0;
+ }
+ }
+ else {
+ /* normal mode */
+ while (1) {
+ int c = lexGetc();
+ switch(c) {
+ case ':': {
+ /* consume all line separator(s) adjacent to each other */
+ /* ignoring linesep immediately after colon. */
+ c = lexLookahead();
+ while (PL_strchr("\n",(char)c)) {
+ lexSkipLookahead();
+ c = lexLookahead();
+ ++mime_lineNum;
+ }
+ DBG_(("db: COLON\n"));
+ return COLON;
+ }
+ case ';':
+ DBG_(("db: SEMICOLON\n"));
+ return SEMICOLON;
+ case '=':
+ DBG_(("db: EQ\n"));
+ return EQ;
+ /* ignore whitespace in this mode */
+ case '\t':
+ case ' ': continue;
+ case '\n': {
+ ++mime_lineNum;
+ continue;
+ }
+ case EOF: return 0;
+ break;
+ default: {
+ lexPushLookaheadc(c);
+ if (isalpha(c)) {
+ char *t = lexGetWord();
+ yylval.str = t;
+ if (!PL_strcasecmp(t, "BEGIN")) {
+ return match_begin_end_name(0);
+ }
+ else if (!PL_strcasecmp(t,"END")) {
+ return match_begin_end_name(1);
+ }
+ else {
+ DBG_(("db: ID '%s'\n", t));
+ return ID;
+ }
+ }
+ else {
+ /* unknown token */
+ return 0;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+
+/***************************************************************************/
+/*** Public Functions ****/
+/***************************************************************************/
+
+static VObject* parse_MIMEHelper()
+ {
+ ObjStackTop = -1;
+ mime_numErrors = 0;
+ mime_lineNum = 1;
+ vObjList = 0;
+ curObj = 0;
+
+ if (yyparse() != 0)
+ return 0;
+
+ finiLex();
+ return vObjList;
+ }
+
+/******************************************************************************/
+VObject* parse_MIME(const char *input, unsigned long len)
+ {
+ initLex(input, len);
+ return parse_MIMEHelper();
+ }
+
+static MimeErrorHandler mimeErrorHandler;
+
+void registerMimeErrorHandler(MimeErrorHandler me)
+ {
+ mimeErrorHandler = me;
+ }
+
+void mime_error(const char *s)
+{
+ char msg[256];
+ if (mimeErrorHandler) {
+ PR_snprintf(msg, sizeof(msg), "%s at line %d", s, mime_lineNum);
+ mimeErrorHandler(msg);
+ }
+}
+
+/*#line 1221 "y_tab.c"*/
+#define YYABORT goto yyabort
+#define YYACCEPT goto yyaccept
+#define YYERROR goto yyerrlab
+int
+yyparse()
+{
+ int yym, yyn, yystate;
+#if YYDEBUG
+ char *yys;
+ extern char *getenv();
+
+ if (yys = getenv("YYDEBUG"))
+ {
+ yyn = *yys;
+ if (yyn >= '0' && yyn <= '9')
+ yydebug = yyn - '0';
+ }
+#endif
+
+ yynerrs = 0;
+ yyerrflag = 0;
+ yychar = (-1);
+
+ yyssp = yyss;
+ yyvsp = yyvs;
+ *yyssp = yystate = 0;
+
+yyloop:
+ if ((yyn = yydefred[yystate])) goto yyreduce;
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYPR_MAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("yydebug: state %d, reading %d (%s)\n", yystate,
+ yychar, yys);
+ }
+#endif
+ }
+ if ((yyn = yysindex[yystate]) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: state %d, shifting to state %d\n",
+ yystate, yytable[yyn]);
+#endif
+ if (yyssp >= yyss + yystacksize - 1)
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = yystate = yytable[yyn];
+ *++yyvsp = yylval;
+ yychar = (-1);
+ if (yyerrflag > 0) --yyerrflag;
+ goto yyloop;
+ }
+ if ((yyn = yyrindex[yystate]) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+ {
+ yyn = yytable[yyn];
+ goto yyreduce;
+ }
+ if (yyerrflag) goto yyinrecovery;
+#ifdef lint
+ goto yynewerror;
+#endif
+/*yynewerror: */
+ yyerror("syntax error");
+#ifdef lint
+ goto yyerrlab;
+#endif
+yyerrlab:
+ ++yynerrs;
+yyinrecovery:
+ if (yyerrflag < 3)
+ {
+ yyerrflag = 3;
+ for (;;)
+ {
+ if ((yyn = yysindex[*yyssp]) && (yyn += YYERRCODE) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == YYERRCODE)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: state %d, error recovery shifting\
+ to state %d\n", *yyssp, yytable[yyn]);
+#endif
+ if (yyssp >= yyss + yystacksize - 1)
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = yystate = yytable[yyn];
+ *++yyvsp = yylval;
+ goto yyloop;
+ }
+ else
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: error recovery discarding state %d\n",
+ *yyssp);
+#endif
+ if (yyssp <= yyss) goto yyabort;
+ --yyssp;
+ --yyvsp;
+ }
+ }
+ }
+ else
+ {
+ if (yychar == 0) goto yyabort;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYPR_MAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("yydebug: state %d, error recovery discards token %d (%s)\n",
+ yystate, yychar, yys);
+ }
+#endif
+ yychar = (-1);
+ goto yyloop;
+ }
+yyreduce:
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: state %d, reducing by rule %d (%s)\n",
+ yystate, yyn, yyrule[yyn]);
+#endif
+ yym = yylen[yyn];
+ yyval = yyvsp[1-yym];
+ switch (yyn)
+ {
+case 2:
+/*#line 282 "vcc.y"*/
+{ addList(&vObjList, yyvsp[0].vobj ); curObj = 0; }
+break;
+case 4:
+/*#line 285 "vcc.y"*/
+{ addList(&vObjList, yyvsp[0].vobj ); curObj = 0; }
+break;
+case 7:
+/*#line 294 "vcc.y"*/
+{
+ lexPushMode(L_VCARD);
+ if (!pushVObject(VCCardProp)) YYERROR;
+ }
+break;
+case 8:
+/*#line 299 "vcc.y"*/
+{
+ lexPopMode(0);
+ yyval.vobj = popVObject();
+ }
+break;
+case 9:
+/*#line 304 "vcc.y"*/
+{
+ lexPushMode(L_VCARD);
+ if (!pushVObject(VCCardProp)) YYERROR;
+ }
+break;
+case 10:
+/*#line 309 "vcc.y"*/
+{
+ lexPopMode(0);
+ yyval.vobj = popVObject();
+ }
+break;
+case 13:
+/*#line 320 "vcc.y"*/
+{
+ lexPushMode(L_VALUES);
+ }
+break;
+case 14:
+/*#line 324 "vcc.y"*/
+{
+ if (lexWithinMode(L_BASE64) || lexWithinMode(L_QUOTED_PRINTABLE))
+ lexPopMode(0);
+ lexPopMode(0);
+ }
+break;
+case 16:
+/*#line 332 "vcc.y"*/
+{
+ enterProps(yyvsp[0].str );
+ }
+break;
+case 18:
+/*#line 337 "vcc.y"*/
+{
+ enterProps(yyvsp[0].str );
+ }
+break;
+case 22:
+/*#line 350 "vcc.y"*/
+{
+ enterAttr(yyvsp[0].str ,0);
+ }
+break;
+case 23:
+/*#line 354 "vcc.y"*/
+{
+ enterAttr(yyvsp[-2].str ,yyvsp[0].str );
+
+ }
+break;
+case 25:
+/*#line 363 "vcc.y"*/
+{ enterValues(yyvsp[-1].str ); }
+break;
+case 27:
+/*#line 365 "vcc.y"*/
+{ enterValues(yyvsp[0].str ); }
+break;
+case 29:
+/*#line 370 "vcc.y"*/
+{ yyval.str = 0; }
+break;
+case 30:
+/*#line 375 "vcc.y"*/
+{ if (!pushVObject(VCCalProp)) YYERROR; }
+break;
+case 31:
+/*#line 378 "vcc.y"*/
+{ yyval.vobj = popVObject(); }
+break;
+case 32:
+/*#line 380 "vcc.y"*/
+{ if (!pushVObject(VCCalProp)) YYERROR; }
+break;
+case 33:
+/*#line 382 "vcc.y"*/
+{ yyval.vobj = popVObject(); }
+break;
+case 39:
+/*#line 397 "vcc.y"*/
+{
+ lexPushMode(L_VEVENT);
+ if (!pushVObject(VCEventProp)) YYERROR;
+ }
+break;
+case 40:
+/*#line 403 "vcc.y"*/
+{
+ lexPopMode(0);
+ popVObject();
+ }
+break;
+case 41:
+/*#line 408 "vcc.y"*/
+{
+ lexPushMode(L_VEVENT);
+ if (!pushVObject(VCEventProp)) YYERROR;
+ }
+break;
+case 42:
+/*#line 413 "vcc.y"*/
+{
+ lexPopMode(0);
+ popVObject();
+ }
+break;
+case 43:
+/*#line 421 "vcc.y"*/
+{
+ lexPushMode(L_VTODO);
+ if (!pushVObject(VCTodoProp)) YYERROR;
+ }
+break;
+case 44:
+/*#line 427 "vcc.y"*/
+{
+ lexPopMode(0);
+ popVObject();
+ }
+break;
+case 45:
+/*#line 432 "vcc.y"*/
+{
+ lexPushMode(L_VTODO);
+ if (!pushVObject(VCTodoProp)) YYERROR;
+ }
+break;
+case 46:
+/*#line 437 "vcc.y"*/
+{
+ lexPopMode(0);
+ popVObject();
+ }
+break;
+/*#line 1520 "y_tab.c"*/
+ }
+ yyssp -= yym;
+ yystate = *yyssp;
+ yyvsp -= yym;
+ yym = yylhs[yyn];
+ if (yystate == 0 && yym == 0)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: after reduction, shifting from state 0 to\
+ state %d\n", YYFINAL);
+#endif
+ yystate = YYFINAL;
+ *++yyssp = YYFINAL;
+ *++yyvsp = yyval;
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYPR_MAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("yydebug: state %d, reading %d (%s)\n",
+ YYFINAL, yychar, yys);
+ }
+#endif
+ }
+ if (yychar == 0) goto yyaccept;
+ goto yyloop;
+ }
+ if ((yyn = yygindex[yym]) && (yyn += yystate) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yystate)
+ yystate = yytable[yyn];
+ else
+ yystate = yydgoto[yym];
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: after reduction, shifting from state %d \
+to state %d\n", *yyssp, yystate);
+#endif
+ if (yyssp >= yyss + yystacksize - 1)
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = yystate;
+ *++yyvsp = yyval;
+ goto yyloop;
+yyoverflow:
+ yyerror("yacc stack overflow");
+yyabort:
+ return (1);
+yyaccept:
+ return (0);
+}
diff --git a/mailnews/addrbook/src/nsVCard.h b/mailnews/addrbook/src/nsVCard.h
new file mode 100644
index 000000000..041cf0042
--- /dev/null
+++ b/mailnews/addrbook/src/nsVCard.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/***************************************************************************
+(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+
+For purposes of this license notice, the term Licensors shall mean,
+collectively, Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+The term Licensor shall mean any of the Licensors.
+
+Subject to acceptance of the following conditions, permission is hereby
+granted by Licensors without the need for written agreement and without
+license or royalty fees, to use, copy, modify and distribute this
+software for any purpose.
+
+The above copyright notice and the following four paragraphs must be
+reproduced in all copies of this software and any software including
+this software.
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE
+ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR
+MODIFICATIONS.
+
+IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT,
+INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT
+OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.
+
+The software is provided with RESTRICTED RIGHTS. Use, duplication, or
+disclosure by the government are subject to restrictions set forth in
+DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable.
+
+***************************************************************************/
+
+#ifndef __VCC_H__
+#define __VCC_H__ 1
+
+#include "nsVCardObj.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VObject* parse_MIME(const char *input, unsigned long len);
+
+typedef void (*MimeErrorHandler)(char *);
+
+void registerMimeErrorHandler(MimeErrorHandler);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __VCC_H__ */
diff --git a/mailnews/addrbook/src/nsVCardObj.cpp b/mailnews/addrbook/src/nsVCardObj.cpp
new file mode 100644
index 000000000..bf8ceb2fb
--- /dev/null
+++ b/mailnews/addrbook/src/nsVCardObj.cpp
@@ -0,0 +1,1330 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/***************************************************************************
+(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+
+For purposes of this license notice, the term Licensors shall mean,
+collectively, Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+The term Licensor shall mean any of the Licensors.
+
+Subject to acceptance of the following conditions, permission is hereby
+granted by Licensors without the need for written agreement and without
+license or royalty fees, to use, copy, modify and distribute this
+software for any purpose.
+
+The above copyright notice and the following four paragraphs must be
+reproduced in all copies of this software and any software including
+this software.
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE
+ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR
+MODIFICATIONS.
+
+IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT,
+INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT
+OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.
+
+The software is provided with RESTRICTED RIGHTS. Use, duplication, or
+disclosure by the government are subject to restrictions set forth in
+DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable.
+
+***************************************************************************/
+
+/*
+ * doc: vobject and APIs to construct vobject, APIs pretty print
+ * vobject, and convert a vobject into its textual representation.
+ */
+
+#include "prlog.h"
+#include "nsVCard.h"
+#include "nsVCardObj.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "nsStringGlue.h"
+
+/* debugging utilities */
+#define DBG_(x)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ char **fieldedProp;
+
+#ifdef __cplusplus
+ }
+#endif
+
+
+
+static VObject* newVObject_(const char *id);
+#if 0
+static int vObjectValueType(VObject *o);
+static void initVObjectIterator(VObjectIterator *i, VObject *o);
+#endif
+
+/*----------------------------------------------------------------------
+ The following functions involve with memory allocation:
+ newVObject
+ deleteVObject
+ dupStr
+ deleteString
+ newStrItem
+ deleteStrItem
+ ----------------------------------------------------------------------*/
+
+static bool needsQuotedPrintable (const char *s)
+{
+ const unsigned char *p = (const unsigned char *)s;
+
+ while (*p) {
+ if (*p & 0x80 || *p == '\015' || *p == '\012')
+ return true;
+ p++;
+ }
+
+ return false;
+}
+
+VObject* newVObject_(const char *id)
+{
+ VObject *p = (VObject*) new(VObject);
+ p->next = 0;
+ p->id = id;
+ p->prop = 0;
+ VALUE_TYPE(p) = 0;
+ ANY_VALUE_OF(p) = 0;
+ return p;
+}
+
+VObject* newVObject(const char *id)
+{
+ return newVObject_(lookupStr(id));
+}
+
+void deleteVObject(VObject *p)
+{
+ unUseStr(p->id);
+ delete (p);
+}
+
+char* dupStr(const char *s, unsigned int size)
+{
+ char *t;
+ if (size == 0) {
+ size = PL_strlen(s);
+ }
+ t = (char*)PR_CALLOC(size+1);
+ if (t) {
+ memcpy(t,s,size);
+ t[size] = 0;
+ return t;
+ }
+ else {
+ return (char*)0;
+ }
+}
+
+static StrItem* newStrItem(const char *s, StrItem *next)
+{
+ StrItem *p = (StrItem*)PR_CALLOC(sizeof(StrItem));
+ p->next = next;
+ p->s = s;
+ p->refCnt = 1;
+ return p;
+}
+
+extern "C"
+void deleteString(char *p)
+{
+ if (p)
+ PR_Free ((void*)p);
+}
+
+extern "C"
+void deleteStrItem(StrItem *p)
+{
+ if (p)
+ PR_FREEIF (p);
+}
+
+
+
+/*----------------------------------------------------------------------
+ The following function provide accesses to VObject's value.
+ ----------------------------------------------------------------------*/
+
+const char* vObjectName(VObject *o)
+{
+ return NAME_OF(o);
+}
+
+void setVObjectName(VObject *o, const char* id)
+{
+ NAME_OF(o) = id;
+}
+
+const char* vObjectStringZValue(VObject *o)
+{
+ return STRINGZ_VALUE_OF(o);
+}
+
+void setVObjectStringZValue(VObject *o, const char *s)
+{
+ STRINGZ_VALUE_OF(o) = dupStr(s,0);
+ VALUE_TYPE(o) = VCVT_STRINGZ;
+}
+
+void setVObjectStringZValue_(VObject *o, const char *s)
+{
+ STRINGZ_VALUE_OF(o) = s;
+ VALUE_TYPE(o) = VCVT_STRINGZ;
+}
+
+const vwchar_t* vObjectUStringZValue(VObject *o)
+{
+ return USTRINGZ_VALUE_OF(o);
+}
+
+void setVObjectUStringZValue(VObject *o, const vwchar_t *s)
+{
+ USTRINGZ_VALUE_OF(o) = (vwchar_t*) dupStr((char*)s,(uStrLen(s)+1)*2);
+ VALUE_TYPE(o) = VCVT_USTRINGZ;
+}
+
+void setVObjectUStringZValue_(VObject *o, const vwchar_t *s)
+{
+ USTRINGZ_VALUE_OF(o) = s;
+ VALUE_TYPE(o) = VCVT_USTRINGZ;
+}
+
+unsigned int vObjectIntegerValue(VObject *o)
+{
+ return INTEGER_VALUE_OF(o);
+}
+
+void setVObjectIntegerValue(VObject *o, unsigned int i)
+{
+ INTEGER_VALUE_OF(o) = i;
+ VALUE_TYPE(o) = VCVT_UINT;
+}
+
+unsigned long vObjectLongValue(VObject *o)
+{
+ return LONG_VALUE_OF(o);
+}
+
+void setVObjectLongValue(VObject *o, unsigned long l)
+{
+ LONG_VALUE_OF(o) = l;
+ VALUE_TYPE(o) = VCVT_ULONG;
+}
+
+void* vObjectAnyValue(VObject *o)
+{
+ return ANY_VALUE_OF(o);
+}
+
+void setVObjectAnyValue(VObject *o, void *t)
+{
+ ANY_VALUE_OF(o) = t;
+ VALUE_TYPE(o) = VCVT_RAW;
+}
+
+VObject* vObjectVObjectValue(VObject *o)
+{
+ return VOBJECT_VALUE_OF(o);
+}
+
+void setVObjectVObjectValue(VObject *o, VObject *p)
+{
+ VOBJECT_VALUE_OF(o) = p;
+ VALUE_TYPE(o) = VCVT_VOBJECT;
+}
+
+#if 0
+int vObjectValueType(VObject *o)
+{
+ return VALUE_TYPE(o);
+}
+#endif
+
+
+/*----------------------------------------------------------------------
+ The following functions can be used to build VObject.
+ ----------------------------------------------------------------------*/
+
+VObject* addVObjectProp(VObject *o, VObject *p)
+{
+ /* circular link list pointed to tail */
+ /*
+ o {next,id,prop,val}
+ V
+ pn {next,id,prop,val}
+ V
+ ...
+ p1 {next,id,prop,val}
+ V
+ pn
+ -->
+ o {next,id,prop,val}
+ V
+ pn {next,id,prop,val}
+ V
+ p {next,id,prop,val}
+ ...
+ p1 {next,id,prop,val}
+ V
+ pn
+ */
+
+ VObject *tail = o->prop;
+ if (tail) {
+ p->next = tail->next;
+ o->prop = tail->next = p;
+ }
+ else {
+ o->prop = p->next = p;
+ }
+ return p;
+}
+
+VObject* addProp(VObject *o, const char *id)
+{
+ return addVObjectProp(o,newVObject(id));
+}
+
+VObject* addProp_(VObject *o, const char *id)
+{
+ return addVObjectProp(o,newVObject_(id));
+}
+
+void addList(VObject **o, VObject *p)
+{
+ p->next = 0;
+ if (*o == 0) {
+ *o = p;
+ }
+ else {
+ VObject *t = *o;
+ while (t->next) {
+ t = t->next;
+ }
+ t->next = p;
+ }
+}
+
+VObject* nextVObjectInList(VObject *o)
+{
+ return o->next;
+}
+
+VObject* setValueWithSize_(VObject *prop, void *val, unsigned int size)
+{
+ VObject *sizeProp;
+ setVObjectAnyValue(prop, val);
+ sizeProp = addProp(prop,VCDataSizeProp);
+ setVObjectLongValue(sizeProp, size);
+ return prop;
+}
+
+VObject* setValueWithSize(VObject *prop, void *val, unsigned int size)
+{
+ void *p = dupStr((const char *)val,size);
+ return setValueWithSize_(prop,p,p?size:0);
+}
+
+void initPropIterator(VObjectIterator *i, VObject *o)
+{
+ i->start = o->prop;
+ i->next = 0;
+}
+
+#if 0
+void initVObjectIterator(VObjectIterator *i, VObject *o)
+{
+ i->start = o->next;
+ i->next = 0;
+}
+#endif
+
+int moreIteration(VObjectIterator *i)
+{
+ return (i->start && (i->next==0 || i->next!=i->start));
+}
+
+VObject* nextVObject(VObjectIterator *i)
+{
+ if (i->start && i->next != i->start) {
+ if (i->next == 0) {
+ i->next = i->start->next;
+ return i->next;
+ }
+ else {
+ i->next = i->next->next;
+ return i->next;
+ }
+ }
+ else return (VObject*)0;
+}
+
+VObject* isAPropertyOf(VObject *o, const char *id)
+{
+ VObjectIterator i;
+ initPropIterator(&i,o);
+ while (moreIteration(&i)) {
+ VObject *each = nextVObject(&i);
+ if (!PL_strcasecmp(id,each->id))
+ return each;
+ }
+ return (VObject*)0;
+}
+
+VObject* addGroup(VObject *o, const char *g)
+{
+ /*
+ a.b.c
+ -->
+ prop(c)
+ prop(VCGrouping=b)
+ prop(VCGrouping=a)
+ */
+ char *dot = PL_strrchr(g,'.');
+ if (dot) {
+ VObject *p, *t;
+ char *gs, *n = dot+1;
+ gs = dupStr(g,0); /* so we can write to it. */
+ t = p = addProp_(o,lookupProp(n));
+ dot = PL_strrchr(gs,'.');
+ *dot = 0;
+ do {
+ dot = PL_strrchr(gs,'.');
+ if (dot) {
+ n = dot+1;
+ *dot=0;
+ }
+ else
+ n = gs;
+ /* property(VCGroupingProp=n);
+ * and the value may have VCGrouping property
+ */
+ t = addProp(t,VCGroupingProp);
+ setVObjectStringZValue(t,lookupProp_(n));
+ } while (n != gs);
+ deleteString(gs);
+ return p;
+ }
+ else
+ return addProp_(o,lookupProp(g));
+}
+
+VObject* addPropValue(VObject *o, const char *p, const char *v)
+{
+ VObject *prop;
+ prop = addProp(o,p);
+ if (v) {
+ setVObjectUStringZValue_(prop, fakeUnicode(v,0));
+ if (needsQuotedPrintable (v)) {
+ if (PL_strcasecmp (VCCardProp, vObjectName(o)) == 0)
+ addProp (prop, VCQuotedPrintableProp);
+ else
+ addProp (o, VCQuotedPrintableProp);
+ }
+ }
+ else
+ setVObjectUStringZValue_(prop, fakeUnicode("",0));
+
+ return prop;
+}
+
+VObject* addPropSizedValue_(VObject *o, const char *p, const char *v,
+ unsigned int size)
+{
+ VObject *prop;
+ prop = addProp(o,p);
+ setValueWithSize_(prop, (void*)v, size);
+ return prop;
+}
+
+VObject* addPropSizedValue(VObject *o, const char *p, const char *v,
+ unsigned int size)
+{
+ return addPropSizedValue_(o,p,dupStr(v,size),size);
+}
+
+void cleanVObject(VObject *o)
+{
+ if (o == 0) return;
+ if (o->prop) {
+ /* destroy time: cannot use the iterator here.
+ Have to break the cycle in the circular link
+ list and turns it into regular NULL-terminated
+ list -- since at some point of destruction,
+ the reference entry for the iterator to work
+ will not longer be valid.
+ */
+ VObject *p;
+ p = o->prop->next;
+ o->prop->next = 0;
+ do {
+ VObject *t = p->next;
+ cleanVObject(p);
+ p = t;
+ } while (p);
+ }
+ switch (VALUE_TYPE(o)) {
+ case VCVT_USTRINGZ:
+ case VCVT_STRINGZ:
+ case VCVT_RAW:
+ /* assume they are all allocated by malloc. */
+ if ((char*) STRINGZ_VALUE_OF(o))
+ PR_Free ((char*)STRINGZ_VALUE_OF(o));
+ break;
+ case VCVT_VOBJECT:
+ cleanVObject(VOBJECT_VALUE_OF(o));
+ break;
+ }
+ deleteVObject(o);
+}
+
+void cleanVObjects(VObject *list)
+{
+ while (list) {
+ VObject *t = list;
+ list = nextVObjectInList(list);
+ cleanVObject(t);
+ }
+}
+
+/*----------------------------------------------------------------------
+ The following is a String Table Facilities.
+ ----------------------------------------------------------------------*/
+
+#define STRTBLSIZE 255
+
+static StrItem *strTbl[STRTBLSIZE];
+
+static unsigned int hashStr(const char *s)
+{
+ unsigned int h = 0;
+ int i;
+ for (i=0;s[i];i++) {
+ h += s[i]*i;
+ }
+ return h % STRTBLSIZE;
+}
+
+void unUseStr(const char *s)
+{
+ StrItem *t, *p;
+ unsigned int h = hashStr(s);
+ if ((t = strTbl[h]) != 0) {
+ p = t;
+ do {
+ if (PL_strcasecmp(t->s,s) == 0) {
+ t->refCnt--;
+ if (t->refCnt == 0) {
+ if (t == strTbl[h]) {
+ strTbl[h] = t->next;
+ }
+ else {
+ p->next = t->next;
+ }
+ deleteString((char *)t->s);
+ deleteStrItem(t);
+ return;
+ }
+ }
+ p = t;
+ t = t->next;
+ } while (t);
+ }
+}
+
+struct PreDefProp {
+ const char *name;
+ const char *alias;
+ const char** fields;
+ unsigned int flags;
+ };
+
+/* flags in PreDefProp */
+#define PD_BEGIN 0x1
+#define PD_INTERNAL 0x2
+
+static const char *adrFields[] = {
+ VCPostalBoxProp,
+ VCExtAddressProp,
+ VCStreetAddressProp,
+ VCCityProp,
+ VCRegionProp,
+ VCPostalCodeProp,
+ VCCountryNameProp,
+ 0
+};
+
+static const char *nameFields[] = {
+ VCFamilyNameProp,
+ VCGivenNameProp,
+ VCAdditionalNamesProp,
+ VCNamePrefixesProp,
+ VCNameSuffixesProp,
+ NULL
+ };
+
+static const char *orgFields[] = {
+ VCOrgNameProp,
+ VCOrgUnitProp,
+ VCOrgUnit2Prop,
+ VCOrgUnit3Prop,
+ VCOrgUnit4Prop,
+ NULL
+ };
+
+static const char *AAlarmFields[] = {
+ VCRunTimeProp,
+ VCSnoozeTimeProp,
+ VCRepeatCountProp,
+ VCAudioContentProp,
+ 0
+ };
+
+static const char *coolTalkFields[] = {
+ VCCooltalkAddress,
+ VCUseServer,
+ 0
+ };
+
+/* ExDate -- has unamed fields */
+/* RDate -- has unamed fields */
+
+static const char *DAlarmFields[] = {
+ VCRunTimeProp,
+ VCSnoozeTimeProp,
+ VCRepeatCountProp,
+ VCDisplayStringProp,
+ 0
+ };
+
+static const char *MAlarmFields[] = {
+ VCRunTimeProp,
+ VCSnoozeTimeProp,
+ VCRepeatCountProp,
+ VCEmailAddressProp,
+ VCNoteProp,
+ 0
+ };
+
+static const char *PAlarmFields[] = {
+ VCRunTimeProp,
+ VCSnoozeTimeProp,
+ VCRepeatCountProp,
+ VCProcedureNameProp,
+ 0
+ };
+
+static struct PreDefProp propNames[] = {
+ { VC7bitProp, 0, 0, 0 },
+ { VC8bitProp, 0, 0, 0 },
+ { VCAAlarmProp, 0, AAlarmFields, 0 },
+ { VCAdditionalNamesProp, 0, 0, 0 },
+ { VCAdrProp, 0, adrFields, 0 },
+ { VCAgentProp, 0, 0, 0 },
+ { VCAIFFProp, 0, 0, 0 },
+ { VCAOLProp, 0, 0, 0 },
+ { VCAppleLinkProp, 0, 0, 0 },
+ { VCAttachProp, 0, 0, 0 },
+ { VCAttendeeProp, 0, 0, 0 },
+ { VCATTMailProp, 0, 0, 0 },
+ { VCAudioContentProp, 0, 0, 0 },
+ { VCAVIProp, 0, 0, 0 },
+ { VCBase64Prop, 0, 0, 0 },
+ { VCBBSProp, 0, 0, 0 },
+ { VCBirthDateProp, 0, 0, 0 },
+ { VCBMPProp, 0, 0, 0 },
+ { VCBodyProp, 0, 0, 0 },
+ { VCBusinessRoleProp, 0, 0, 0 },
+ { VCCalProp, 0, 0, PD_BEGIN },
+ { VCCaptionProp, 0, 0, 0 },
+ { VCCardProp, 0, 0, PD_BEGIN },
+ { VCCarProp, 0, 0, 0 },
+ { VCCategoriesProp, 0, 0, 0 },
+ { VCCellularProp, 0, 0, 0 },
+ { VCCGMProp, 0, 0, 0 },
+ { VCCharSetProp, 0, 0, 0 },
+ { VCCIDProp, VCContentIDProp, 0, 0 },
+ { VCCISProp, 0, 0, 0 },
+ { VCCityProp, 0, 0, 0 },
+ { VCClassProp, 0, 0, 0 },
+ { VCCommentProp, 0, 0, 0 },
+ { VCCompletedProp, 0, 0, 0 },
+ { VCContentIDProp, 0, 0, 0 },
+ { VCCountryNameProp, 0, 0, 0 },
+ { VCDAlarmProp, 0, DAlarmFields, 0 },
+ { VCDataSizeProp, 0, 0, PD_INTERNAL },
+ { VCDayLightProp, 0, 0 ,0 },
+ { VCDCreatedProp, 0, 0, 0 },
+ { VCDeliveryLabelProp, 0, 0, 0 },
+ { VCDescriptionProp, 0, 0, 0 },
+ { VCDIBProp, 0, 0, 0 },
+ { VCDisplayStringProp, 0, 0, 0 },
+ { VCDomesticProp, 0, 0, 0 },
+ { VCDTendProp, 0, 0, 0 },
+ { VCDTstartProp, 0, 0, 0 },
+ { VCDueProp, 0, 0, 0 },
+ { VCEmailAddressProp, 0, 0, 0 },
+ { VCEncodingProp, 0, 0, 0 },
+ { VCEndProp, 0, 0, 0 },
+ { VCEventProp, 0, 0, PD_BEGIN },
+ { VCEWorldProp, 0, 0, 0 },
+ { VCExNumProp, 0, 0, 0 },
+ { VCExpDateProp, 0, 0, 0 },
+ { VCExpectProp, 0, 0, 0 },
+ { VCExtAddressProp, 0, 0, 0 },
+ { VCFamilyNameProp, 0, 0, 0 },
+ { VCFaxProp, 0, 0, 0 },
+ { VCFullNameProp, 0, 0, 0 },
+ { VCGeoLocationProp, 0, 0, 0 },
+ { VCGeoProp, 0, 0, 0 },
+ { VCGIFProp, 0, 0, 0 },
+ { VCGivenNameProp, 0, 0, 0 },
+ { VCGroupingProp, 0, 0, 0 },
+ { VCHomeProp, 0, 0, 0 },
+ { VCIBMMailProp, 0, 0, 0 },
+ { VCInlineProp, 0, 0, 0 },
+ { VCInternationalProp, 0, 0, 0 },
+ { VCInternetProp, 0, 0, 0 },
+ { VCISDNProp, 0, 0, 0 },
+ { VCJPEGProp, 0, 0, 0 },
+ { VCLanguageProp, 0, 0, 0 },
+ { VCLastModifiedProp, 0, 0, 0 },
+ { VCLastRevisedProp, 0, 0, 0 },
+ { VCLocationProp, 0, 0, 0 },
+ { VCLogoProp, 0, 0, 0 },
+ { VCMailerProp, 0, 0, 0 },
+ { VCMAlarmProp, 0, MAlarmFields, 0 },
+ { VCMCIMailProp, 0, 0, 0 },
+ { VCMessageProp, 0, 0, 0 },
+ { VCMETProp, 0, 0, 0 },
+ { VCModemProp, 0, 0, 0 },
+ { VCMPEG2Prop, 0, 0, 0 },
+ { VCMPEGProp, 0, 0, 0 },
+ { VCMSNProp, 0, 0, 0 },
+ { VCNamePrefixesProp, 0, 0, 0 },
+ { VCNameProp, 0, nameFields, 0 },
+ { VCNameSuffixesProp, 0, 0, 0 },
+ { VCNoteProp, 0, 0, 0 },
+ { VCOrgNameProp, 0, 0, 0 },
+ { VCOrgProp, 0, orgFields, 0 },
+ { VCOrgUnit2Prop, 0, 0, 0 },
+ { VCOrgUnit3Prop, 0, 0, 0 },
+ { VCOrgUnit4Prop, 0, 0, 0 },
+ { VCOrgUnitProp, 0, 0, 0 },
+ { VCPagerProp, 0, 0, 0 },
+ { VCPAlarmProp, 0, PAlarmFields, 0 },
+ { VCParcelProp, 0, 0, 0 },
+ { VCPartProp, 0, 0, 0 },
+ { VCPCMProp, 0, 0, 0 },
+ { VCPDFProp, 0, 0, 0 },
+ { VCPGPProp, 0, 0, 0 },
+ { VCPhotoProp, 0, 0, 0 },
+ { VCPICTProp, 0, 0, 0 },
+ { VCPMBProp, 0, 0, 0 },
+ { VCPostalBoxProp, 0, 0, 0 },
+ { VCPostalCodeProp, 0, 0, 0 },
+ { VCPostalProp, 0, 0, 0 },
+ { VCPowerShareProp, 0, 0, 0 },
+ { VCPreferredProp, 0, 0, 0 },
+ { VCPriorityProp, 0, 0, 0 },
+ { VCProcedureNameProp, 0, 0, 0 },
+ { VCProdIdProp, 0, 0, 0 },
+ { VCProdigyProp, 0, 0, 0 },
+ { VCPronunciationProp, 0, 0, 0 },
+ { VCPSProp, 0, 0, 0 },
+ { VCPublicKeyProp, 0, 0, 0 },
+ { VCQPProp, VCQuotedPrintableProp, 0, 0 },
+ { VCQuickTimeProp, 0, 0, 0 },
+ { VCQuotedPrintableProp, 0, 0, 0 },
+ { VCRDateProp, 0, 0, 0 },
+ { VCRegionProp, 0, 0, 0 },
+ { VCRelatedToProp, 0, 0, 0 },
+ { VCRepeatCountProp, 0, 0, 0 },
+ { VCResourcesProp, 0, 0, 0 },
+ { VCRNumProp, 0, 0, 0 },
+ { VCRoleProp, 0, 0, 0 },
+ { VCRRuleProp, 0, 0, 0 },
+ { VCRSVPProp, 0, 0, 0 },
+ { VCRunTimeProp, 0, 0, 0 },
+ { VCSequenceProp, 0, 0, 0 },
+ { VCSnoozeTimeProp, 0, 0, 0 },
+ { VCStartProp, 0, 0, 0 },
+ { VCStatusProp, 0, 0, 0 },
+ { VCStreetAddressProp, 0, 0, 0 },
+ { VCSubTypeProp, 0, 0, 0 },
+ { VCSummaryProp, 0, 0, 0 },
+ { VCTelephoneProp, 0, 0, 0 },
+ { VCTIFFProp, 0, 0, 0 },
+ { VCTimeZoneProp, 0, 0, 0 },
+ { VCTitleProp, 0, 0, 0 },
+ { VCTLXProp, 0, 0, 0 },
+ { VCTodoProp, 0, 0, PD_BEGIN },
+ { VCTranspProp, 0, 0, 0 },
+ { VCUniqueStringProp, 0, 0, 0 },
+ { VCURLProp, 0, 0, 0 },
+ { VCURLValueProp, 0, 0, 0 },
+ { VCValueProp, 0, 0, 0 },
+ { VCVersionProp, 0, 0, 0 },
+ { VCVideoProp, 0, 0, 0 },
+ { VCVoiceProp, 0, 0, 0 },
+ { VCWAVEProp, 0, 0, 0 },
+ { VCWMFProp, 0, 0, 0 },
+ { VCWorkProp, 0, 0, 0 },
+ { VCX400Prop, 0, 0, 0 },
+ { VCX509Prop, 0, 0, 0 },
+ { VCXRuleProp, 0, 0, 0 },
+ { VCCooltalk, 0, coolTalkFields, 0 },
+ { VCCooltalkAddress, 0, 0, 0 },
+ { VCUseServer, 0, 0, 0 },
+ { VCUseHTML, 0, 0, 0 },
+ { 0,0,0,0 }
+ };
+
+
+static struct PreDefProp* lookupPropInfo(const char* str)
+{
+ /* brute force for now, could use a hash table here. */
+ int i;
+
+ for (i = 0; propNames[i].name; i++)
+ if (PL_strcasecmp(str, propNames[i].name) == 0) {
+ return &propNames[i];
+ }
+
+ return 0;
+}
+
+
+const char* lookupProp_(const char* str)
+{
+ int i;
+
+ for (i = 0; propNames[i].name; i++)
+ if (PL_strcasecmp(str, propNames[i].name) == 0) {
+ const char* s;
+ s = propNames[i].alias?propNames[i].alias:propNames[i].name;
+ return lookupStr(s);
+ }
+ return lookupStr(str);
+}
+
+
+const char* lookupProp(const char* str)
+{
+ int i;
+
+ for (i = 0; propNames[i].name; i++)
+ if (PL_strcasecmp(str, propNames[i].name) == 0) {
+ const char *s;
+ fieldedProp = (char **)propNames[i].fields;
+ s = propNames[i].alias?propNames[i].alias:propNames[i].name;
+ return lookupStr(s);
+ }
+ fieldedProp = 0;
+ return lookupStr(str);
+}
+
+
+/*----------------------------------------------------------------------
+ APIs to Output text form.
+ ----------------------------------------------------------------------*/
+#define OFILE_REALLOC_SIZE 256
+
+static void appendcOFile_(OFile *fp, char c)
+{
+ if (fp->fail)
+ return;
+stuff:
+ if (fp->len+1 < fp->limit) {
+ fp->s[fp->len] = c;
+ fp->len++;
+ return;
+ }
+ else if (fp->alloc) {
+ fp->limit = fp->limit + OFILE_REALLOC_SIZE;
+ char* newBuf = (char *) PR_Realloc(fp->s, fp->limit);
+ if (newBuf) {
+ fp->s = newBuf;
+ goto stuff;
+ }
+ }
+ if (fp->alloc)
+ PR_FREEIF(fp->s);
+ fp->s = 0;
+ fp->fail = 1;
+}
+
+static void appendcOFile(OFile *fp, char c)
+{
+/* int i = 0; */
+ if (c == '\n') {
+ /* write out as <CR><LF> */
+ /* for (i = 0; i < LINEBREAK_LEN; i++)
+ appendcOFile_(fp,LINEBREAK [ i ]); */
+ appendcOFile_(fp,0xd);
+ appendcOFile_(fp,0xa);
+ }
+ else
+ appendcOFile_(fp,c);
+}
+
+static void appendsOFile(OFile *fp, const char *s)
+{
+ int i, slen;
+ slen = PL_strlen (s);
+ for (i=0; i<slen; i++) {
+ appendcOFile(fp,s[i]);
+ }
+}
+
+static void initMemOFile(OFile *fp, char *s, int len)
+{
+ fp->s = s;
+ fp->len = 0;
+ fp->limit = s?len:0;
+ fp->alloc = s?0:1;
+ fp->fail = 0;
+}
+
+
+static int writeBase64(OFile *fp, unsigned char *s, long len)
+{
+ long cur = 0;
+ int i, numQuads = 0;
+ unsigned long trip;
+ unsigned char b;
+ char quad[5];
+#define PR_MAXQUADS 16
+
+ quad[4] = 0;
+
+ while (cur < len) {
+ /* collect the triplet of bytes into 'trip' */
+ trip = 0;
+ for (i = 0; i < 3; i++) {
+ b = (cur < len) ? *(s + cur) : 0;
+ cur++;
+ trip = trip << 8 | b;
+ }
+ /* fill in 'quad' with the appropriate four characters */
+ for (i = 3; i >= 0; i--) {
+ b = (unsigned char)(trip & 0x3F);
+ trip = trip >> 6;
+ if ((3 - i) < (cur - len))
+ quad[i] = '='; /* pad char */
+ else if (b < 26) quad[i] = (char)b + 'A';
+ else if (b < 52) quad[i] = (char)(b - 26) + 'a';
+ else if (b < 62) quad[i] = (char)(b - 52) + '0';
+ else if (b == 62) quad[i] = '+';
+ else quad[i] = '/';
+ }
+ /* now output 'quad' with appropriate whitespace and line ending */
+ appendsOFile(fp, (numQuads == 0 ? " " : ""));
+ appendsOFile(fp, quad);
+ appendsOFile(fp, ((cur >= len)?"\n" :(numQuads==PR_MAXQUADS-1?"\n" : "")));
+ numQuads = (numQuads + 1) % PR_MAXQUADS;
+ }
+ appendcOFile(fp,'\n');
+
+ return 1;
+}
+
+static void writeQPString(OFile *fp, const char *s)
+{
+ const unsigned char *p = (const unsigned char *)s;
+ int current_column = 0;
+ static const char hexdigits[] = "0123456789ABCDEF";
+ bool white = false;
+ bool contWhite = false;
+ bool mb_p = false;
+
+ if (needsQuotedPrintable (s))
+ {
+ while (*p) {
+ if (*p == '\r' || *p == '\n')
+ {
+ /* Whitespace cannot be allowed to occur at the end of the line.
+ So we encode " \n" as " =\n\n", that is, the whitespace, a
+ soft line break, and then a hard line break.
+ */
+
+ if (white)
+ {
+ appendcOFile(fp,'=');
+ appendcOFile(fp,'\n');
+ appendcOFile(fp,'\t');
+ appendsOFile(fp,"=0D");
+ appendsOFile(fp,"=0A");
+ appendcOFile(fp,'=');
+ appendcOFile(fp,'\n');
+ appendcOFile(fp,'\t');
+ }
+ else
+ {
+ appendsOFile(fp,"=0D");
+ appendsOFile(fp,"=0A");
+ appendcOFile(fp,'=');
+ appendcOFile(fp,'\n');
+ appendcOFile(fp,'\t');
+ contWhite = false;
+ }
+
+ /* If its CRLF, swallow two chars instead of one. */
+ if (*p == '\r' && *(p+1) == '\n')
+ p++;
+ white = false;
+ current_column = 0;
+ }
+ else
+ {
+ if ((*p >= 33 && *p <= 60) || /* safe printing chars */
+ (*p >= 62 && *p <= 126) ||
+ (mb_p && (*p == 61 || *p == 127 || *p == 0x1B)))
+ {
+ appendcOFile(fp,*p);
+ current_column++;
+ white = false;
+ contWhite = false;
+ }
+ else if (*p == ' ' || *p == '\t') /* whitespace */
+ {
+ if (contWhite)
+ {
+ appendcOFile(fp,'=');
+ appendcOFile(fp,hexdigits[*p >> 4]);
+ appendcOFile(fp,hexdigits[*p & 0xF]);
+ current_column += 3;
+ contWhite = false;
+ }
+ else
+ {
+ appendcOFile(fp,*p);
+ current_column++;
+ }
+ white = true;
+ }
+ else /* print as =FF */
+ {
+ appendcOFile(fp,'=');
+ appendcOFile(fp,hexdigits[*p >> 4]);
+ appendcOFile(fp,hexdigits[*p & 0xF]);
+ current_column += 3;
+ white = false;
+ contWhite = false;
+ }
+
+ NS_ASSERTION(current_column <= 76, "1.10 <rhp@netscape.com> 06 Jan 2000 08:01"); /* Hard limit required by spec */
+
+ if (current_column >= 73 || ((*(p+1) == ' ') && (current_column + 3 >= 73))) /* soft line break: "=\r\n" */
+ {
+ appendcOFile(fp,'=');
+ appendcOFile(fp,'\n');
+ appendcOFile(fp,'\t');
+ current_column = 0;
+ if (white)
+ contWhite = true;
+ else
+ contWhite = false;
+ white = false;
+ }
+ }
+ p++;
+ } /* while */
+ } /* if */
+ else
+ {
+ while (*p) {
+ appendcOFile(fp,*p);
+ p++;
+ }
+ }
+}
+
+
+static void writeValue(OFile *fp, VObject *o, unsigned long size)
+{
+ if (o == 0) return;
+ switch (VALUE_TYPE(o)) {
+ case VCVT_USTRINGZ: {
+ char *s = fakeCString(USTRINGZ_VALUE_OF(o));
+ writeQPString(fp, s);
+ deleteString(s);
+ break;
+ }
+ case VCVT_STRINGZ: {
+ writeQPString(fp, STRINGZ_VALUE_OF(o));
+ break;
+ }
+ case VCVT_UINT: {
+ char buf[11];
+ sprintf(buf,"%u", INTEGER_VALUE_OF(o));
+ appendsOFile(fp, buf);
+ break;
+ }
+ case VCVT_ULONG: {
+ char buf[21];
+ sprintf(buf,"%lu", LONG_VALUE_OF(o));
+ appendsOFile(fp, buf);
+ break;
+ }
+ case VCVT_RAW: {
+ appendcOFile(fp,'\n');
+ writeBase64(fp,(unsigned char*)(ANY_VALUE_OF(o)),size);
+ break;
+ }
+ case VCVT_VOBJECT:
+ appendcOFile(fp,'\n');
+ writeVObject_(fp,VOBJECT_VALUE_OF(o));
+ break;
+ }
+}
+
+static void writeAttrValue(OFile *fp, VObject *o, int* length)
+{
+ int ilen = 0;
+ if (NAME_OF(o)) {
+ struct PreDefProp *pi;
+ pi = lookupPropInfo(NAME_OF(o));
+ if (pi && ((pi->flags & PD_INTERNAL) != 0)) return;
+ appendcOFile(fp,';');
+ if (*length != -1)
+ (*length)++;
+ appendsOFile(fp,NAME_OF(o));
+ if (*length != -1)
+ (*length) += PL_strlen (NAME_OF(o));
+ }
+ else {
+ appendcOFile(fp,';');
+ (*length)++;
+ }
+ if (VALUE_TYPE(o)) {
+ appendcOFile(fp,'=');
+ if (*length != -1) {
+ (*length)++;
+ for (ilen = 0; ilen < MAXMOZPROPNAMESIZE - (*length); ilen++)
+ appendcOFile(fp,' ');
+ }
+ writeValue(fp,o,0);
+ }
+}
+
+static void writeGroup(OFile *fp, VObject *o)
+{
+ nsAutoCString buf(NAME_OF(o));
+
+ while ((o=isAPropertyOf(o,VCGroupingProp)) != 0) {
+ buf.Insert(NS_LITERAL_CSTRING("."), 0);
+ buf.Insert(STRINGZ_VALUE_OF(o), 0);
+ }
+ appendsOFile(fp, buf.get());
+}
+
+static int inList(const char **list, const char *s)
+{
+ if (list == 0) return 0;
+ while (*list) {
+ if (PL_strcasecmp(*list,s) == 0) return 1;
+ list++;
+ }
+ return 0;
+}
+
+static void writeProp(OFile *fp, VObject *o)
+{
+ int length = -1;
+ //int ilen = 0;
+
+ if (NAME_OF(o)) {
+ struct PreDefProp *pi;
+ VObjectIterator t;
+ const char **fields_ = 0;
+ pi = lookupPropInfo(NAME_OF(o));
+ if (pi && ((pi->flags & PD_BEGIN) != 0)) {
+ writeVObject_(fp,o);
+ return;
+ }
+ if (isAPropertyOf(o,VCGroupingProp))
+ writeGroup(fp,o);
+ else
+ appendsOFile(fp,NAME_OF(o));
+ if (pi) fields_ = pi->fields;
+ initPropIterator(&t,o);
+ while (moreIteration(&t)) {
+ const char *s;
+ VObject *eachProp = nextVObject(&t);
+ s = NAME_OF(eachProp);
+ if (PL_strcasecmp(VCGroupingProp,s) && !inList(fields_,s))
+ writeAttrValue(fp,eachProp, &length);
+ }
+ if (fields_) {
+ int i = 0, n = 0;
+ const char** fields = fields_;
+ /* output prop as fields */
+ appendcOFile(fp,':');
+ while (*fields) {
+ VObject *tt = isAPropertyOf(o,*fields);
+ i++;
+ if (tt) n = i;
+ fields++;
+ }
+ fields = fields_;
+ for (i=0;i<n;i++) {
+ writeValue(fp,isAPropertyOf(o,*fields),0);
+ fields++;
+ if (i<(n-1)) appendcOFile(fp,';');
+ }
+ }
+ }
+
+ if (VALUE_TYPE(o)) {
+ unsigned long size = 0;
+ VObject *p = isAPropertyOf(o,VCDataSizeProp);
+ if (p) size = LONG_VALUE_OF(p);
+ appendcOFile(fp,':');
+ writeValue(fp,o,size);
+ }
+ appendcOFile(fp,'\n');
+}
+
+void writeVObject_(OFile *fp, VObject *o)
+{
+ //int ilen = 0;
+ if (NAME_OF(o)) {
+ struct PreDefProp *pi;
+ pi = lookupPropInfo(NAME_OF(o));
+
+ if (pi && ((pi->flags & PD_BEGIN) != 0)) {
+ VObjectIterator t;
+ const char *begin = NAME_OF(o);
+ appendsOFile(fp,"begin:");
+ appendsOFile(fp,begin);
+ appendcOFile(fp,'\n');
+ initPropIterator(&t,o);
+ while (moreIteration(&t)) {
+ VObject *eachProp = nextVObject(&t);
+ writeProp(fp, eachProp);
+ }
+ appendsOFile(fp,"end:");
+ appendsOFile(fp,begin);
+ appendsOFile(fp,"\n\n");
+ }
+ }
+}
+
+char* writeMemVObject(char *s, int *len, VObject *o)
+{
+ OFile ofp;
+ initMemOFile(&ofp,s,len?*len:0);
+ writeVObject_(&ofp,o);
+ if (len) *len = ofp.len;
+ appendcOFile(&ofp,0);
+ return ofp.s;
+}
+
+extern "C"
+char * writeMemoryVObjects(char *s, int *len, VObject *list, bool expandSpaces)
+{
+ OFile ofp;
+ initMemOFile(&ofp,s,len?*len:0);
+ while (list) {
+ writeVObject_(&ofp,list);
+ list = nextVObjectInList(list);
+ }
+ if (len) *len = ofp.len;
+ appendcOFile(&ofp,0);
+ return ofp.s;
+}
+
+/*----------------------------------------------------------------------
+ APIs to do fake Unicode stuff.
+ ----------------------------------------------------------------------*/
+vwchar_t* fakeUnicode(const char *ps, int *bytes)
+{
+ vwchar_t *r, *pw;
+ int len = strlen(ps)+1;
+
+ pw = r = (vwchar_t*)PR_CALLOC(sizeof(vwchar_t)*len);
+ if (bytes)
+ *bytes = len * sizeof(vwchar_t);
+
+ while (*ps) {
+ if (*ps == '\n')
+ *pw = (vwchar_t)0x2028;
+ else if (*ps == '\r')
+ *pw = (vwchar_t)0x2029;
+ else
+ *pw = (vwchar_t)(unsigned char)*ps;
+ ps++; pw++;
+ }
+ *pw = (vwchar_t)0;
+
+ return r;
+}
+
+int uStrLen(const vwchar_t *u)
+{
+ if (!u)
+ return 0;
+ int i = 0;
+ while (*u != (vwchar_t)0) { u++; i++; }
+ return i;
+}
+
+char* fakeCString(const vwchar_t *u)
+{
+ char *s, *t;
+ int len = uStrLen(u) + 1;
+ t = s = (char*)PR_CALLOC(len);
+ if (u) {
+ while (*u) {
+ if (*u == (vwchar_t)0x2028)
+ *t = '\n';
+ else if (*u == (vwchar_t)0x2029)
+ *t = '\r';
+ else
+ *t = (char)*u;
+ u++; t++;
+ }
+ }
+ *t = 0;
+ return s;
+}
+
+const char* lookupStr(const char *s)
+{
+ StrItem *t;
+ unsigned int h = hashStr(s);
+ if ((t = strTbl[h]) != 0) {
+ do {
+ if (PL_strcasecmp(t->s,s) == 0) {
+ t->refCnt++;
+ return t->s;
+ }
+ t = t->next;
+ } while (t);
+ }
+ s = dupStr(s,0);
+ strTbl[h] = newStrItem(s,strTbl[h]);
+ return s;
+}
diff --git a/mailnews/addrbook/src/nsVCardObj.h b/mailnews/addrbook/src/nsVCardObj.h
new file mode 100644
index 000000000..b7ca36997
--- /dev/null
+++ b/mailnews/addrbook/src/nsVCardObj.h
@@ -0,0 +1,396 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/***************************************************************************
+(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+
+For purposes of this license notice, the term Licensors shall mean,
+collectively, Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+The term Licensor shall mean any of the Licensors.
+
+Subject to acceptance of the following conditions, permission is hereby
+granted by Licensors without the need for written agreement and without
+license or royalty fees, to use, copy, modify and distribute this
+software for any purpose.
+
+The above copyright notice and the following four paragraphs must be
+reproduced in all copies of this software and any software including
+this software.
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE
+ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR
+MODIFICATIONS.
+
+IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT,
+INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT
+OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.
+
+The software is provided with RESTRICTED RIGHTS. Use, duplication, or
+disclosure by the government are subject to restrictions set forth in
+DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable.
+
+***************************************************************************/
+
+/*
+
+The vCard/vCalendar C interface is implemented in the set
+of files as follows:
+
+vcc.y, yacc source, and vcc.c, the yacc output you will use
+implements the core parser
+
+vobject.c implements an API that insulates the caller from
+the parser and changes in the vCard/vCalendar BNF
+
+port.h defines compilation environment dependent stuff
+
+vcc.h and vobject.h are header files for their .c counterparts
+
+vcaltmp.h and vcaltmp.c implement vCalendar "macro" functions
+which you may find useful.
+
+test.c is a standalone test driver that exercises some of
+the features of the APIs provided. Invoke test.exe on a
+VCARD/VCALENDAR input text file and you will see the pretty
+print output of the internal representation (this pretty print
+output should give you a good idea of how the internal
+representation looks like -- there is one such output in the
+following too). Also, a file with the .out suffix is generated
+to show that the internal representation can be written back
+in the original text format.
+
+For more information on this API see the readme.txt file
+which accompanied this distribution.
+
+ Also visit:
+
+ http://www.versit.com
+ http://www.ralden.com
+
+*/
+
+
+#ifndef __VOBJECT_H__
+#define __VOBJECT_H__ 1
+
+/*
+Unfortunately, on the Mac (and possibly other platforms) with our current, out-dated
+libraries (Plauger), |wchar_t| is defined incorrectly, which breaks vcards.
+
+We can't fix Plauger because it doesn't come with source. Later, when we
+upgrade to MSL, we can make this evil hack go away. In the mean time,
+vcards are not allowed to use the (incorrectly defined) |wchar_t| type. Instead,
+they will use an appropriately defined local type |vwchar_t|.
+*/
+
+typedef wchar_t vwchar_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define VC7bitProp "7bit"
+#define VC8bitProp "8bit"
+#define VCAAlarmProp "aalarm"
+#define VCAdditionalNamesProp "addn"
+#define VCAdrProp "adr"
+#define VCAgentProp "agent"
+#define VCAIFFProp "aiff"
+#define VCAOLProp "aol"
+#define VCAppleLinkProp "applelink"
+#define VCAttachProp "attach"
+#define VCAttendeeProp "attendee"
+#define VCATTMailProp "attmail"
+#define VCAudioContentProp "audiocontent"
+#define VCAVIProp "avi"
+#define VCBase64Prop "base64"
+#define VCBBSProp "bbs"
+#define VCBirthDateProp "bday"
+#define VCBMPProp "bmp"
+#define VCBodyProp "body"
+#define VCBusinessRoleProp "role"
+#define VCCalProp "vcalendar"
+#define VCCaptionProp "cap"
+#define VCCardProp "vcard"
+#define VCCarProp "car"
+#define VCCategoriesProp "categories"
+#define VCCellularProp "cell"
+#define VCCGMProp "cgm"
+#define VCCharSetProp "cs"
+#define VCCIDProp "cid"
+#define VCCISProp "cis"
+#define VCCityProp "l"
+#define VCClassProp "class"
+#define VCCommentProp "note"
+#define VCCompletedProp "completed"
+#define VCContentIDProp "content-id"
+#define VCCountryNameProp "c"
+#define VCDAlarmProp "dalarm"
+#define VCDataSizeProp "datasize"
+#define VCDayLightProp "daylight"
+#define VCDCreatedProp "dcreated"
+#define VCDeliveryLabelProp "label"
+#define VCDescriptionProp "description"
+#define VCDIBProp "dib"
+#define VCDisplayStringProp "displaystring"
+#define VCDomesticProp "dom"
+#define VCDTendProp "dtend"
+#define VCDTstartProp "dtstart"
+#define VCDueProp "due"
+#define VCEmailAddressProp "email"
+#define VCEncodingProp "encoding"
+#define VCEndProp "end"
+#define VCEventProp "vevent"
+#define VCEWorldProp "eworld"
+#define VCExNumProp "exnum"
+#define VCExpDateProp "exdate"
+#define VCExpectProp "expect"
+#define VCExtAddressProp "ext add"
+#define VCFamilyNameProp "f"
+#define VCFaxProp "fax"
+#define VCFullNameProp "fn"
+#define VCGeoProp "geo"
+#define VCGeoLocationProp "geo"
+#define VCGIFProp "gif"
+#define VCGivenNameProp "g"
+#define VCGroupingProp "grouping"
+#define VCHomeProp "home"
+#define VCIBMMailProp "ibmmail"
+#define VCInlineProp "inline"
+#define VCInternationalProp "intl"
+#define VCInternetProp "internet"
+#define VCISDNProp "isdn"
+#define VCJPEGProp "jpeg"
+#define VCLanguageProp "lang"
+#define VCLastModifiedProp "last-modified"
+#define VCLastRevisedProp "rev"
+#define VCLocationProp "location"
+#define VCLogoProp "logo"
+#define VCMailerProp "mailer"
+#define VCMAlarmProp "malarm"
+#define VCMCIMailProp "mcimail"
+#define VCMessageProp "msg"
+#define VCMETProp "met"
+#define VCModemProp "modem"
+#define VCMPEG2Prop "mpeg2"
+#define VCMPEGProp "mpeg"
+#define VCMSNProp "msn"
+#define VCNamePrefixesProp "npre"
+#define VCNameProp "n"
+#define VCNameSuffixesProp "nsuf"
+#define VCNoteProp "note"
+#define VCOrgNameProp "orgname"
+#define VCOrgProp "org"
+#define VCOrgUnit2Prop "oun2"
+#define VCOrgUnit3Prop "oun3"
+#define VCOrgUnit4Prop "oun4"
+#define VCOrgUnitProp "oun"
+#define VCPagerProp "pager"
+#define VCPAlarmProp "palarm"
+#define VCParcelProp "parcel"
+#define VCPartProp "part"
+#define VCPCMProp "pcm"
+#define VCPDFProp "pdf"
+#define VCPGPProp "pgp"
+#define VCPhotoProp "photo"
+#define VCPICTProp "pict"
+#define VCPMBProp "pmb"
+#define VCPostalBoxProp "box"
+#define VCPostalCodeProp "pc"
+#define VCPostalProp "postal"
+#define VCPowerShareProp "powershare"
+#define VCPreferredProp "pref"
+#define VCPriorityProp "priority"
+#define VCProcedureNameProp "procedurename"
+#define VCProdIdProp "prodid"
+#define VCProdigyProp "prodigy"
+#define VCPronunciationProp "sound"
+#define VCPSProp "ps"
+#define VCPublicKeyProp "key"
+#define VCQPProp "qp"
+#define VCQuickTimeProp "qtime"
+#define VCQuotedPrintableProp "quoted-printable"
+#define VCRDateProp "rdate"
+#define VCRegionProp "r"
+#define VCRelatedToProp "related-to"
+#define VCRepeatCountProp "repeatcount"
+#define VCResourcesProp "resources"
+#define VCRNumProp "rnum"
+#define VCRoleProp "role"
+#define VCRRuleProp "rrule"
+#define VCRSVPProp "rsvp"
+#define VCRunTimeProp "runtime"
+#define VCSequenceProp "sequence"
+#define VCSnoozeTimeProp "snoozetime"
+#define VCStartProp "start"
+#define VCStatusProp "status"
+#define VCStreetAddressProp "street"
+#define VCSubTypeProp "subtype"
+#define VCSummaryProp "summary"
+#define VCTelephoneProp "tel"
+#define VCTIFFProp "tiff"
+#define VCTimeZoneProp "tz"
+#define VCTitleProp "title"
+#define VCTLXProp "tlx"
+#define VCTodoProp "vtodo"
+#define VCTranspProp "transp"
+#define VCUniqueStringProp "uid"
+#define VCURLProp "url"
+#define VCURLValueProp "urlval"
+#define VCValueProp "value"
+#define VCVersionProp "version"
+#define VCVideoProp "video"
+#define VCVoiceProp "voice"
+#define VCWAVEProp "wave"
+#define VCWMFProp "wmf"
+#define VCWorkProp "work"
+#define VCX400Prop "x400"
+#define VCX509Prop "x509"
+#define VCXRuleProp "xrule"
+#define VCCooltalk "x-mozilla-cpt"
+#define VCCooltalkAddress "x-moxilla-cpadr"
+#define VCUseServer "x-mozilla-cpsrv"
+#define VCUseHTML "x-mozilla-html"
+
+/* return type of vObjectValueType: */
+#define VCVT_NOVALUE 0
+ /* if the VObject has no value associated with it. */
+#define VCVT_STRINGZ 1
+ /* if the VObject has value set by setVObjectStringZValue. */
+#define VCVT_USTRINGZ 2
+ /* if the VObject has value set by setVObjectUStringZValue. */
+#define VCVT_UINT 3
+ /* if the VObject has value set by setVObjectIntegerValue. */
+#define VCVT_ULONG 4
+ /* if the VObject has value set by setVObjectLongValue. */
+#define VCVT_RAW 5
+ /* if the VObject has value set by setVObjectAnyValue. */
+#define VCVT_VOBJECT 6
+ /* if the VObject has value set by setVObjectVObjectValue. */
+
+#define NAME_OF(o) o->id
+#define VALUE_TYPE(o) o->valType
+#define STRINGZ_VALUE_OF(o) o->val.strs
+#define USTRINGZ_VALUE_OF(o) o->val.ustrs
+#define INTEGER_VALUE_OF(o) o->val.i
+#define LONG_VALUE_OF(o) o->val.l
+#define ANY_VALUE_OF(o) o->val.any
+#define VOBJECT_VALUE_OF(o) o->val.vobj
+
+typedef struct VObject VObject;
+
+typedef union ValueItem {
+ const char *strs;
+ const vwchar_t *ustrs;
+ unsigned int i;
+ unsigned long l;
+ void *any;
+ VObject *vobj;
+ } ValueItem;
+
+struct VObject {
+ VObject *next;
+ const char *id;
+ VObject *prop;
+ unsigned short valType;
+ ValueItem val;
+ };
+
+typedef struct StrItem StrItem;
+
+struct StrItem {
+ StrItem *next;
+ const char *s;
+ unsigned int refCnt;
+ };
+
+typedef struct OFile {
+ char *s;
+ int len;
+ int limit;
+ int alloc:1;
+ int fail:1;
+ } OFile;
+
+typedef struct VObjectIterator {
+ VObject* start;
+ VObject* next;
+ } VObjectIterator;
+
+VObject* newVObject(const char *id);
+void deleteVObject(VObject *p);
+char* dupStr(const char *s, unsigned int size);
+extern "C" void deleteString(char *p);
+void unUseStr(const char *s);
+
+void setVObjectName(VObject *o, const char* id);
+void setVObjectStringZValue(VObject *o, const char *s);
+void setVObjectStringZValue_(VObject *o, const char *s);
+void setVObjectUStringZValue(VObject *o, const vwchar_t *s);
+void setVObjectUStringZValue_(VObject *o, const vwchar_t *s);
+void setVObjectIntegerValue(VObject *o, unsigned int i);
+void setVObjectLongValue(VObject *o, unsigned long l);
+void setVObjectAnyValue(VObject *o, void *t);
+VObject* setValueWithSize(VObject *prop, void *val, unsigned int size);
+VObject* setValueWithSize_(VObject *prop, void *val, unsigned int size);
+
+const char* vObjectName(VObject *o);
+const char* vObjectStringZValue(VObject *o);
+const vwchar_t* vObjectUStringZValue(VObject *o);
+unsigned int vObjectIntegerValue(VObject *o);
+unsigned long vObjectLongValue(VObject *o);
+void* vObjectAnyValue(VObject *o);
+VObject* vObjectVObjectValue(VObject *o);
+void setVObjectVObjectValue(VObject *o, VObject *p);
+
+VObject* addVObjectProp(VObject *o, VObject *p);
+VObject* addProp(VObject *o, const char *id);
+VObject* addProp_(VObject *o, const char *id);
+VObject* addPropValue(VObject *o, const char *p, const char *v);
+VObject* addPropSizedValue_(VObject *o, const char *p, const char *v, unsigned int size);
+VObject* addPropSizedValue(VObject *o, const char *p, const char *v, unsigned int size);
+VObject* addGroup(VObject *o, const char *g);
+void addList(VObject **o, VObject *p);
+
+VObject* isAPropertyOf(VObject *o, const char *id);
+
+VObject* nextVObjectInList(VObject *o);
+void initPropIterator(VObjectIterator *i, VObject *o);
+int moreIteration(VObjectIterator *i);
+VObject* nextVObject(VObjectIterator *i);
+
+void writeVObject_(OFile *fp, VObject *o);
+char* writeMemVObject(char *s, int *len, VObject *o);
+extern "C" char* writeMemoryVObjects(char *s, int *len, VObject *list, bool expandSpaces);
+
+const char* lookupStr(const char *s);
+
+void cleanVObject(VObject *o);
+void cleanVObjects(VObject *list);
+
+const char* lookupProp(const char* str);
+const char* lookupProp_(const char* str);
+
+vwchar_t* fakeUnicode(const char *ps, int *bytes);
+int uStrLen(const vwchar_t *u);
+char* fakeCString(const vwchar_t *u);
+
+#define MAXPROPNAMESIZE 256
+#define MAXMOZPROPNAMESIZE 16
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __VOBJECT_H__ */
+
+
diff --git a/mailnews/addrbook/src/nsWabAddressBook.cpp b/mailnews/addrbook/src/nsWabAddressBook.cpp
new file mode 100644
index 000000000..9c991ddf5
--- /dev/null
+++ b/mailnews/addrbook/src/nsWabAddressBook.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <tchar.h>
+#include "nsWabAddressBook.h"
+#include "mozilla/Logging.h"
+#include <algorithm>
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gWabAddressBookLog
+ = PR_NewLogModule("nsWabAddressBookLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(gWabAddressBookLog, mozilla::LogLevel::Debug, args)
+
+using namespace mozilla;
+
+HMODULE nsWabAddressBook::mLibrary = NULL ;
+int32_t nsWabAddressBook::mLibUsage = 0 ;
+LPWABOPEN nsWabAddressBook::mWABOpen = NULL ;
+LPWABOBJECT nsWabAddressBook::mRootSession = NULL ;
+LPADRBOOK nsWabAddressBook::mRootBook = NULL ;
+
+BOOL nsWabAddressBook::LoadWabLibrary(void)
+{
+ if (mLibrary) { ++ mLibUsage ; return TRUE ; }
+ // We try to fetch the location of the WAB DLL from the registry
+ TCHAR wabDLLPath [MAX_PATH] ;
+ DWORD keyType = 0 ;
+ ULONG byteCount = sizeof(wabDLLPath) ;
+ HKEY keyHandle = NULL ;
+ wabDLLPath [MAX_PATH - 1] = 0 ;
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WAB_DLL_PATH_KEY, 0, KEY_READ, &keyHandle) == ERROR_SUCCESS) {
+ RegQueryValueEx(keyHandle, "", NULL, &keyType, (LPBYTE) wabDLLPath, &byteCount) ;
+ if (keyType == REG_EXPAND_SZ) {
+ // Expand the environment variables
+ DWORD bufferSize = ExpandEnvironmentStrings(wabDLLPath, NULL, 0);
+ if (bufferSize && bufferSize < MAX_PATH) {
+ TCHAR tmp[MAX_PATH];
+ ExpandEnvironmentStrings(wabDLLPath, tmp, bufferSize);
+ _tcscpy(wabDLLPath, tmp);
+ }
+ else {
+ return FALSE;
+ }
+ }
+ }
+ else {
+ if (GetSystemDirectory(wabDLLPath, MAX_PATH)) {
+ _tcsncat(wabDLLPath, WAB_DLL_NAME,
+ std::min(_tcslen(WAB_DLL_NAME), MAX_PATH - _tcslen(wabDLLPath) - 1));
+ }
+ else {
+ return FALSE;
+ }
+ }
+ if (keyHandle) { RegCloseKey(keyHandle) ; }
+ mLibrary = LoadLibrary( (lstrlen(wabDLLPath)) ? wabDLLPath : WAB_DLL_NAME );
+ if (!mLibrary) { return FALSE ; }
+ ++ mLibUsage ;
+ mWABOpen = reinterpret_cast<LPWABOPEN>(GetProcAddress(mLibrary, "WABOpen")) ;
+ if (!mWABOpen) { return FALSE ; }
+ HRESULT retCode = mWABOpen(&mRootBook, &mRootSession, NULL, 0) ;
+
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot initialize WAB %08x.\n", retCode)) ; return FALSE ;
+ }
+ return TRUE ;
+}
+
+void nsWabAddressBook::FreeWabLibrary(void)
+{
+ if (mLibrary) {
+ if (-- mLibUsage == 0) {
+ if (mRootBook) { mRootBook->Release() ; }
+ if (mRootSession) { mRootSession->Release() ; }
+ FreeLibrary(mLibrary) ;
+ mLibrary = NULL ;
+ }
+ }
+}
+
+nsWabAddressBook::nsWabAddressBook(void)
+: nsAbWinHelper()
+{
+ BOOL result = Initialize() ;
+
+ NS_ASSERTION(result == TRUE, "Couldn't initialize Wab Helper") ;
+ MOZ_COUNT_CTOR(nsWabAddressBook) ;
+}
+
+nsWabAddressBook::~nsWabAddressBook(void)
+{
+ MutexAutoLock guard(*mMutex) ;
+ FreeWabLibrary() ;
+ MOZ_COUNT_DTOR(nsWabAddressBook) ;
+}
+
+BOOL nsWabAddressBook::Initialize(void)
+{
+ if (mAddressBook) { return TRUE ; }
+ MutexAutoLock guard(*mMutex) ;
+
+ if (!LoadWabLibrary()) {
+ PRINTF(("Cannot load library.\n")) ;
+ return FALSE ;
+ }
+ mAddressBook = mRootBook ;
+ return TRUE ;
+}
+
+void nsWabAddressBook::AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer)
+{
+ mRootSession->AllocateBuffer(aByteCount, aBuffer) ;
+}
+
+void nsWabAddressBook::FreeBuffer(LPVOID aBuffer)
+{
+ mRootSession->FreeBuffer(aBuffer) ;
+}
+
+
+
+
+
+
diff --git a/mailnews/addrbook/src/nsWabAddressBook.h b/mailnews/addrbook/src/nsWabAddressBook.h
new file mode 100644
index 000000000..35b76b213
--- /dev/null
+++ b/mailnews/addrbook/src/nsWabAddressBook.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsWabAddressBook_h___
+#define nsWabAddressBook_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbWinHelper.h"
+#include <wab.h>
+
+class nsWabAddressBook : public nsAbWinHelper
+{
+public :
+ nsWabAddressBook(void) ;
+ virtual ~nsWabAddressBook(void) ;
+
+protected :
+ // Session and address book that will be shared by all instances
+ // (see nsMapiAddressBook.h for details)
+ static LPWABOBJECT mRootSession ;
+ static LPADRBOOK mRootBook ;
+ // Class members to handle library loading/entry points
+ static int32_t mLibUsage ;
+ static HMODULE mLibrary ;
+ static LPWABOPEN mWABOpen ;
+
+ // Load the WAB environment
+ BOOL Initialize(void) ;
+ // Allocation of a buffer for transmission to interfaces
+ virtual void AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) override;
+ // Destruction of a buffer provided by the interfaces
+ virtual void FreeBuffer(LPVOID aBuffer) override;
+ // Manage the library
+ static BOOL LoadWabLibrary(void) ;
+ static void FreeWabLibrary(void) ;
+
+private :
+} ;
+
+// Additional definitions for WAB stuff. These properties are
+// only defined with regards to the default character sizes,
+// and not in two _A and _W versions...
+#define PR_BUSINESS_ADDRESS_CITY_A PR_LOCALITY_A
+#define PR_BUSINESS_ADDRESS_COUNTRY_A PR_COUNTRY_A
+#define PR_BUSINESS_ADDRESS_POSTAL_CODE_A PR_POSTAL_CODE_A
+#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A PR_STATE_OR_PROVINCE_A
+#define PR_BUSINESS_ADDRESS_STREET_A PR_STREET_ADDRESS_A
+
+#define PR_BUSINESS_ADDRESS_CITY_W PR_LOCALITY_W
+#define PR_BUSINESS_ADDRESS_COUNTRY_W PR_COUNTRY_W
+#define PR_BUSINESS_ADDRESS_POSTAL_CODE_W PR_POSTAL_CODE_W
+#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W PR_STATE_OR_PROVINCE_W
+#define PR_BUSINESS_ADDRESS_STREET_W PR_STREET_ADDRESS_W
+
+#endif // nsWABAddressBook_h___
+