diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mailnews/addrbook/src/nsAbWinHelper.cpp | 1491 |
1 files changed, 1491 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/src/nsAbWinHelper.cpp b/comm/mailnews/addrbook/src/nsAbWinHelper.cpp new file mode 100644 index 0000000000..79379c45c7 --- /dev/null +++ b/comm/mailnews/addrbook/src/nsAbWinHelper.cpp @@ -0,0 +1,1491 @@ +/* -*- 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_IMessage +#define USES_IID_IMAPIFolder +#define USES_IID_IMAPIContainer +#define USES_IID_IABContainer +#define USES_IID_IMAPITable +#define USES_IID_IDistList + +#include "nsAbWinHelper.h" +#include "nsMapiAddressBook.h" + +#include <mapiguid.h> + +#include "mozilla/Logging.h" + +#define PRINT_TO_CONSOLE 0 +#if PRINT_TO_CONSOLE +# define PRINTF(args) printf args +#else +static mozilla::LazyLogModule gAbWinHelperLog("AbWinHelper"); +# define PRINTF(args) MOZ_LOG(gAbWinHelperLog, mozilla::LogLevel::Debug, args) +#endif + +// 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; } + tInterface Get(void) const { 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); +} + +void nsMapiEntry::Move(nsMapiEntry& target, nsMapiEntry& source) { + target.mByteCount = source.mByteCount; + target.mEntryId = source.mEntryId; + source.mByteCount = 0; + source.mEntryId = NULL; +} + +nsMapiEntry::~nsMapiEntry(void) { + Assign(0, NULL); + MOZ_COUNT_DTOR(nsMapiEntry); +} + +void nsMapiEntry::Assign(ULONG aByteCount, LPENTRYID aEntryId) { + assignEntryID(mEntryId, aEntryId, aByteCount); + mByteCount = aByteCount; +} + +void nsMapiEntry::Assign(const nsCString& aString) { + Assign(0, NULL); + ULONG byteCount = aString.Length() / 2; + + if ((aString.Length() & 0x01) != 0) { + // Something wrong here, we should always get an even number of hex digits. + byteCount += 1; + } + unsigned char* currentTarget = new unsigned char[byteCount]; + + mByteCount = byteCount; + mEntryId = reinterpret_cast<LPENTRYID>(currentTarget); + ULONG j = 0; + for (uint32_t i = 0; i < aString.Length(); i += 2) { + char c1 = aString.CharAt(i); + char c2 = i + 1 < aString.Length() ? aString.CharAt(i + 1) : '0'; + // clang-format off + currentTarget[j] = + ((c1 <= '9' ? c1 - '0' : c1 - 'A' + 10) << 4) | + (c2 <= '9' ? c2 - '0' : c2 - 'A' + 10); + // clang-format on + j++; + } +} + +void nsMapiEntry::ToString(nsCString& aString) const { + aString.Truncate(); + aString.SetCapacity(mByteCount * 2); + char twoBytes[3]; + + for (ULONG i = 0; i < mByteCount; i++) { + sprintf(twoBytes, "%02X", (reinterpret_cast<unsigned char*>(mEntryId))[i]); + aString.Append(twoBytes); + } +} + +void nsMapiEntry::Dump(void) const { + PRINTF(("%lu\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; + } +} + +// Microsoft distinguishes between address book entries and contacts. +// Address book entries are of class IMailUser and are stored in containers +// of class IABContainer. +// Local contacts are stored in the "contacts folder" of class IMAPIFolder and +// are of class IMessage with "message class" IPM.Contact. +// For local address books the entry ID of the contact can be derived from the +// entry ID of the address book entry and vice versa. +// Most attributes can be retrieved from both classes with some exceptions: +// The primary e-mail address is only stored on the IMailUser, the contact +// has three named email properties (which are not used so far). +// The birthday is only stored on the contact. +// `OpenMAPIObject()` can open the address book entry as well as the contact, +// to open the concact it needs to get the message store from via the +// address book container (or "directory" in Thunderbird terms). +// Apart from Microsoft documentation, the best source of information +// is the MAPI programmers mailing list at MAPI-L@PEACH.EASE.LSOFT.COM. +// All the information that was needed to "refresh" the MAPI implementation +// in Thunderbird was obtained via these threads: +// https://peach.ease.lsoft.com/scripts/wa-PEACH.exe?A2=2012&L=MAPI-L&D=0&P=20988415 +// https://peach.ease.lsoft.com/scripts/wa-PEACH.exe?A2=2101&L=MAPI-L&D=0&P=21034512 + +// Some stuff to access the entry ID of the contact (IMessage, IPM.Contact) +// from the address book entry ID (IMailUser). +// The address book entry ID has the following structure, see: +// https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcdata/c33d5b9c-d044-4727-96e2-2051f8419ab1 +#define ABENTRY_FLAGS_LENGTH 4 +#define CONTAB_PROVIDER_ID \ + "\xFE\x42\xAA\x0A\x18\xC7\x1A\x10\xE8\x85\x0B\x65\x1C\x24\x00\x00" +#define CONTAB_PROVIDER_ID_LENGTH 16 +#define ABENTRY_VERSION "\x03\x00\x00\x00" +#define ABENTRY_VERSION_LENGTH 4 +#define ABENTRY_TYPE "\x04\x00\x00\x00" +#define ABENTRY_TYPE_LENGTH 4 + +struct AbEntryId { + BYTE flags[ABENTRY_FLAGS_LENGTH]; + BYTE provider[CONTAB_PROVIDER_ID_LENGTH]; + BYTE version[ABENTRY_VERSION_LENGTH]; + BYTE type[ABENTRY_TYPE_LENGTH]; + ULONG index; + ULONG length; + BYTE idBytes[]; +}; + +// Some stuff to access the entry IDs of members in a distribution list +// (IMessage, IPM.DistList): +// https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxocntc/02656215-1cb0-4b06-a077-b07e756216be +// Also handy the reference to the so-called "one off" members: +// https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcdata/b32d23af-85f6-4e92-8387-53a1950ae7ba +#define DLENTRY_FLAGS_LENGTH 4 +#define DL_PROVIDER_ID \ + "\xC0\x91\xAD\xD3\x51\x9D\xCF\x11\xA4\xA9\x00\xAA\x00\x47\xFA\xA4" +#define DL_PROVIDER_ID_LENGTH 16 +#define DLENTRY_TYPE_LENGTH 1 +struct DlEntryId { + BYTE flags[DLENTRY_FLAGS_LENGTH]; + BYTE provider[DL_PROVIDER_ID_LENGTH]; + BYTE type[DLENTRY_TYPE_LENGTH]; + BYTE idBytes[]; +}; + +#define DLENTRY_OO_FLAGS_LENGTH 4 +#define DL_OO_PROVIDER_ID \ + "\x81\x2B\x1F\xA4\xBE\xA3\x10\x19\x9D\x6E\x00\xDD\x01\x0F\x54\x02" +#define DL_OO_PROVIDER_ID_LENGTH 16 +struct DlEntryIdOo { + BYTE flags[DLENTRY_OO_FLAGS_LENGTH]; + BYTE provider[DL_OO_PROVIDER_ID_LENGTH]; + // Note that the documentation specifies a two-byte version followed by a + // two-byte "bit collection", but MFCMapi + // (https://github.com/stephenegriffin/mfcmapi) shows, for example: + // dwBitmask: 0x80010000 = MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO. + // Intel x86 and AMD64 / x86-64 hardware is little-endian, so that + // equates to 0x0000 0x01 0x80 in memory: + // M (1 bit): (mask 0x0100) (MIME) and U (1 bit): (mask 0x0080) (Unicode). + ULONG versionAndBits; + BYTE variable[]; +}; + +using namespace mozilla; + +uint32_t nsAbWinHelper::sEntryCounter = 0; +mozilla::StaticMutex nsAbWinHelper::sMutex; +// 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) : mLastError(S_OK), mAddressBook(NULL) { + MOZ_COUNT_CTOR(nsAbWinHelper); +} + +nsAbWinHelper::~nsAbWinHelper(void) { 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 %08lx.\n", mLastError)); + return FALSE; + } + mLastError = rootFolder->GetHierarchyTable(0, folders); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get hierarchy %08lx.\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 %08lx.\n", mLastError)); + } + folderColumns.cValues = 1; + folderColumns.aulPropTag[0] = PR_ENTRYID; + mLastError = folders->SetColumns(&folderColumns, 0); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set columns %08lx.\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 %08lx.\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; + + nsMapiEntry nullEntry; + if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values, + valueCount)) { + return FALSE; + } + + if (valueCount != 1 || values == NULL) { + PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyString")); + return FALSE; + } + + BOOL success = TRUE; + 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); + } else { + PRINTF(("Unexpected return value for property %08lx (x0A is PT_ERROR).\n", + values->ulPropTag)); + success = FALSE; + } + FreeBuffer(values); + return success; +} + +BOOL nsAbWinHelper::GetPropertyUString(const nsMapiEntry& aObject, + ULONG aPropertyTag, nsString& aName) { + aName.Truncate(); + LPSPropValue values = NULL; + ULONG valueCount = 0; + + nsMapiEntry nullEntry; + if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values, + valueCount)) { + return FALSE; + } + if (valueCount != 1 || values == NULL) { + PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyUString")); + return FALSE; + } + + BOOL success = TRUE; + if (PROP_TYPE(values->ulPropTag) == PT_UNICODE) { + aName = values->Value.lpszW; + } else if (PROP_TYPE(values->ulPropTag) == PT_STRING8) { + aName.AssignASCII(values->Value.lpszA); + } else { + PRINTF(("Unexpected return value for property %08lx (x0A is PT_ERROR).\n", + values->ulPropTag)); + success = FALSE; + } + return success; +} + +BOOL nsAbWinHelper::GetPropertiesUString(const nsMapiEntry& aDir, + const nsMapiEntry& aObject, + const ULONG aPropertyTags[], + ULONG aNbProperties, nsString aNames[], + bool aSuccess[]) { + LPSPropValue values = NULL; + ULONG valueCount = 0; + + if (!GetMAPIProperties(aDir, aObject, aPropertyTags, aNbProperties, values, + valueCount, true)) + return FALSE; + + if (valueCount != aNbProperties || values == NULL) { + PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertiesUString")); + return FALSE; + } + for (ULONG i = 0; i < valueCount; ++i) { + aNames[i].Truncate(); + aSuccess[i] = false; + 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); + aSuccess[i] = true; + } else if (PROP_TYPE(values[i].ulPropTag) == PT_UNICODE) { + aNames[i] = values[i].Value.lpszW; + aSuccess[i] = true; + } else { + PRINTF( + ("Unexpected return value for property %08lx (x0A is PT_ERROR).\n", + values[i].ulPropTag)); + } + } + } + FreeBuffer(values); + return TRUE; +} + +BOOL nsAbWinHelper::GetPropertyDate(const nsMapiEntry& aDir, + const nsMapiEntry& aObject, + bool fromContact, ULONG aPropertyTag, + WORD& aYear, WORD& aMonth, WORD& aDay) { + aYear = 0; + aMonth = 0; + aDay = 0; + LPSPropValue values = NULL; + ULONG valueCount = 0; + + if (!GetMAPIProperties(aDir, aObject, &aPropertyTag, 1, values, valueCount, + fromContact)) { + return FALSE; + } + if (valueCount != 1 || values == NULL) { + PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyDate")); + return FALSE; + } + + BOOL success = TRUE; + if (PROP_TYPE(values->ulPropTag) == PT_SYSTIME) { + SYSTEMTIME readableTime; + if (FileTimeToSystemTime(&values->Value.ft, &readableTime)) { + aYear = readableTime.wYear; + aMonth = readableTime.wMonth; + aDay = readableTime.wDay; + } + } else { + PRINTF(("Cannot retrieve PT_SYSTIME property %08lx (x0A is PT_ERROR).\n", + values->ulPropTag)); + success = FALSE; + } + FreeBuffer(values); + return success; +} + +BOOL nsAbWinHelper::GetPropertyLong(const nsMapiEntry& aObject, + ULONG aPropertyTag, ULONG& aValue) { + aValue = 0; + LPSPropValue values = NULL; + ULONG valueCount = 0; + + nsMapiEntry nullEntry; + if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values, + valueCount)) { + return FALSE; + } + if (valueCount != 1 || values == NULL) { + PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyLong")); + return FALSE; + } + + BOOL success = TRUE; + if (PROP_TYPE(values->ulPropTag) == PT_LONG) { + aValue = values->Value.ul; + } else { + PRINTF(("Cannot retrieve PT_LONG property %08lx (x0A is PT_ERROR).\n", + values->ulPropTag)); + success = FALSE; + } + FreeBuffer(values); + return success; +} + +BOOL nsAbWinHelper::GetPropertyBin(const nsMapiEntry& aObject, + ULONG aPropertyTag, nsMapiEntry& aValue) { + aValue.Assign(0, NULL); + LPSPropValue values = NULL; + ULONG valueCount = 0; + + nsMapiEntry nullEntry; + if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values, + valueCount)) { + return FALSE; + } + if (valueCount != 1 || values == NULL) { + PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyBin")); + return FALSE; + } + + BOOL success = TRUE; + if (PROP_TYPE(values->ulPropTag) == PT_BINARY) { + aValue.Assign(values->Value.bin.cb, + reinterpret_cast<LPENTRYID>(values->Value.bin.lpb)); + } else { + PRINTF(("Cannot retrieve PT_BINARY property %08lx (x0A is PT_ERROR).\n", + values->ulPropTag)); + success = FALSE; + } + + FreeBuffer(values); + return success; +} + +BOOL nsAbWinHelper::GetPropertiesMVBin( + const nsMapiEntry& aDir, const nsMapiEntry& aObject, + const ULONG aPropertyTags[], ULONG aNbProperties, nsMapiEntry* aEntryIDs[], + ULONG aNbElements[], bool aAllocateMore) { + LPSPropValue values = NULL; + ULONG valueCount = 0; + + // Initialise output arrays. + for (ULONG i = 0; i < aNbProperties; i++) { + aEntryIDs[i] = NULL; + aNbElements[i] = 0; + } + + if (!GetMAPIProperties(aDir, aObject, aPropertyTags, aNbProperties, values, + valueCount, true)) { + return FALSE; + } + if (valueCount != aNbProperties || values == NULL) { + PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyMVBin")); + return FALSE; + } + + BOOL success = TRUE; + for (ULONG i = 0; i < valueCount; i++) { + if (PROP_TYPE(values[i].ulPropTag) == PT_MV_BINARY) { + ULONG count = values[i].Value.MVbin.cValues; + PRINTF(("Found %lu members in DL.\n", count)); + aEntryIDs[i] = new nsMapiEntry[aAllocateMore ? count + 1 : count]; + aNbElements[i] = count; + SBinary* currentValue = values[i].Value.MVbin.lpbin; + for (ULONG j = 0; j < count; j++) { + nsMapiEntry& current = aEntryIDs[i][j]; + current.Assign(currentValue->cb, + reinterpret_cast<LPENTRYID>(currentValue->lpb)); + currentValue++; + } + } else { + PRINTF( + ("Cannot retrieve PT_MV_BINARY property %08lx (x0A is PT_ERROR).\n", + values[i].ulPropTag)); + success = FALSE; + } + } + + FreeBuffer(values); + if (!success) { + for (ULONG i = 0; i < aNbProperties; i++) { + if (aNbElements[i] > 0) delete[] aEntryIDs[i]; + aEntryIDs[i] = NULL; + aNbElements[i] = 0; + } + } + return success; +} + +BOOL nsAbWinHelper::SetPropertiesMVBin(const nsMapiEntry& aDir, + const nsMapiEntry& aObject, + const ULONG aPropertyTags[], + ULONG aNbProperties, + nsMapiEntry* aEntryIDs[], + ULONG aNbElements[]) { + LPSPropValue values = new SPropValue[aNbProperties]; + if (!values) return FALSE; + + for (ULONG i = 0; i < aNbProperties; i++) { + values[i].ulPropTag = aPropertyTags[i]; + values[i].Value.MVbin.cValues = aNbElements[i]; + values[i].Value.MVbin.lpbin = new SBinary[aNbElements[i]]; + + SBinary* currentValue = values[i].Value.MVbin.lpbin; + for (ULONG j = 0; j < aNbElements[i]; j++) { + currentValue->cb = aEntryIDs[i][j].mByteCount; + currentValue->lpb = reinterpret_cast<LPBYTE>(aEntryIDs[i][j].mEntryId); + currentValue++; + } + } + BOOL retCode = SetMAPIProperties(aDir, aObject, aNbProperties, values, true); + for (ULONG i = 0; i < aNbProperties; i++) { + delete[] values[i].Value.MVbin.lpbin; + } + delete[] values; + return retCode; +} + +// 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 %08lx.\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 %08lx.\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 %08lx.\n", mLastError)); + return FALSE; + } + return TRUE; +} + +BOOL nsAbWinHelper::GetDlMembersTag(IMAPIProp* aMsg, ULONG& aDlMembersTag, + ULONG& aDlMembersTagOneOff) { + const GUID guid = {0x00062004, + 0x0000, + 0x0000, + {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; + MAPINAMEID nameID; + nameID.lpguid = (GUID*)&guid; + nameID.ulKind = MNID_ID; + LPSPropTagArray lppPropTags; + LPMAPINAMEID lpNameID[1] = {&nameID}; + + // Strangely requesting two tags at the same time doesn't appear to work, + // so request them separately. + // One should be able to set up `lpNameID` with two entries and get two + // tags returned in `lppPropTags`, but sadly the second one is always 0. + nameID.Kind.lID = 0x8055; // PidLidDistributionListMembers + mLastError = aMsg->GetIDsFromNames(1, lpNameID, 0, &lppPropTags); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get DL prop tag %08lx.\n", mLastError)); + return FALSE; + } + aDlMembersTag = lppPropTags[0].aulPropTag[0] | PT_MV_BINARY; + mAddressFreeBuffer(lppPropTags); + + nameID.Kind.lID = 0x8054; // PidLidDistributionListOneOffMembers + mLastError = aMsg->GetIDsFromNames(1, lpNameID, 0, &lppPropTags); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open DL prop tag (one off) %08lx.\n", mLastError)); + return FALSE; + } + aDlMembersTagOneOff = lppPropTags[0].aulPropTag[0] | PT_MV_BINARY; + mAddressFreeBuffer(lppPropTags); + + return TRUE; +} + +BOOL nsAbWinHelper::GetDlNameTag(IMAPIProp* aMsg, ULONG& aDlNameTag) { + const GUID guid = {0x00062004, + 0x0000, + 0x0000, + {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; + MAPINAMEID nameID; + nameID.lpguid = (GUID*)&guid; + nameID.ulKind = MNID_ID; + LPSPropTagArray lppPropTags; + LPMAPINAMEID lpNameID[1] = {&nameID}; + + nameID.Kind.lID = 0x8053; // PidLidDistributionListName + mLastError = aMsg->GetIDsFromNames(1, lpNameID, 0, &lppPropTags); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get DL prop tag %08lx.\n", mLastError)); + return FALSE; + } + aDlNameTag = lppPropTags[0].aulPropTag[0] | PT_UNICODE; + mAddressFreeBuffer(lppPropTags); + + return TRUE; +} + +BOOL nsAbWinHelper::DeleteEntryfromDL(const nsMapiEntry& aTopDir, + const nsMapiEntry& aDistList, + const nsMapiEntry& aEntry) { + // First we need to open the distribution list to get the property tag. + ULONG dlMembersTag = 0; + ULONG dlMembersTagOnOff = 0; + { + // We do this in a block is `msg` going out of scope will release the + // object. + nsMapiInterfaceWrapper<LPMAPIPROP> msg; + mLastError = OpenMAPIObject(aTopDir, aDistList, true, 0, msg); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open DL entry %08lx.\n", mLastError)); + return FALSE; + } + if (!GetDlMembersTag(msg.Get(), dlMembersTag, dlMembersTagOnOff)) + return FALSE; + } + + // This will self-destruct when it goes out of scope. + nsMapiEntryArray dlMembers; + nsMapiEntryArray dlMembersOneOff; + + // Turn IMailUser into IMessage/IPM.Contact. + // Check for magic provider GUID. + struct AbEntryId* abEntryId = (struct AbEntryId*)aEntry.mEntryId; + if (memcmp(abEntryId->provider, CONTAB_PROVIDER_ID, + CONTAB_PROVIDER_ID_LENGTH) != 0) { + PRINTF(("Cannot get to IMessage/IPM.Contact.\n")); + return FALSE; + } + ULONG contactIdLength = abEntryId->length; + LPENTRYID contactId = reinterpret_cast<LPENTRYID>(&(abEntryId->idBytes)); + + ULONG tags[2] = {dlMembersTag, dlMembersTagOnOff}; + nsMapiEntry* values[2]; + ULONG counts[2]; + if (!GetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts)) { + PRINTF(("Cannot get DL members.\n")); + return FALSE; + } + dlMembers.mEntries = values[0]; + dlMembersOneOff.mEntries = values[1]; + dlMembers.mNbEntries = counts[0]; + dlMembersOneOff.mNbEntries = counts[1]; + + if (dlMembers.mNbEntries == 0) return FALSE; + if (dlMembers.mNbEntries != dlMembersOneOff.mNbEntries) { + PRINTF(("DL members and DL one off members have different length.\n")); + return FALSE; + } + + ULONG result; + for (ULONG i = 0; i < dlMembers.mNbEntries; i++) { + struct DlEntryId* dlEntryId = + (struct DlEntryId*)dlMembers.mEntries[i].mEntryId; + if (memcmp(dlEntryId->provider, DL_PROVIDER_ID, DL_PROVIDER_ID_LENGTH) != 0) + continue; + mLastError = mAddressSession->CompareEntryIDs( + contactIdLength, contactId, + dlMembers.mEntries[i].mByteCount - sizeof(struct DlEntryId), + reinterpret_cast<LPENTRYID>(dlEntryId->idBytes), 0, &result); + if (HR_FAILED(mLastError)) { + PRINTF(("CompareEntryIDs failed with %08lx (DeleteEntryfromDL()).\n", + mLastError)); + } + if (result) { + PRINTF(("Found card to be deleted at position %lu.\n", i)); + + // Kill/free entry and shuffle remaining cards down. + dlMembers.mEntries[i].Assign(0, NULL); + dlMembersOneOff.mEntries[i].Assign(0, NULL); + for (ULONG j = i + 1; j < dlMembers.mNbEntries; j++) { + nsMapiEntry::Move(dlMembers.mEntries[j - 1], dlMembers.mEntries[j]); + nsMapiEntry::Move(dlMembersOneOff.mEntries[j - 1], + dlMembersOneOff.mEntries[j]); + } + dlMembers.mNbEntries--; + dlMembersOneOff.mNbEntries--; + + counts[0] = dlMembers.mNbEntries; + counts[1] = dlMembersOneOff.mNbEntries; + if (counts[0] >= 1) { + if (!SetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts)) { + PRINTF(("Cannot set DL members.\n")); + return FALSE; + } + } else { + static const SizedSPropTagArray(2, properties) = { + 2, {dlMembersTag, dlMembersTagOnOff}}; + if (!DeleteMAPIProperties(aTopDir, aDistList, + (LPSPropTagArray)&properties, true)) { + PRINTF(("Cannot delete DL members.\n")); + return FALSE; + } + } + return TRUE; + } + } + return FALSE; +} + +BOOL nsAbWinHelper::AddEntryToDL(const nsMapiEntry& aTopDir, + const nsMapiEntry& aDistList, + const nsMapiEntry& aEntry, + const wchar_t* aDisplay, + const wchar_t* aEmail) { + // First we need to open the distribution list to get the property tag. + ULONG dlMembersTag = 0; + ULONG dlMembersTagOnOff = 0; + { + // We do this in a block is `msg` going out of scope will release the + // object. + nsMapiInterfaceWrapper<LPMAPIPROP> msg; + mLastError = OpenMAPIObject(aTopDir, aDistList, true, 0, msg); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open DL entry %08lx.\n", mLastError)); + return FALSE; + } + if (!GetDlMembersTag(msg.Get(), dlMembersTag, dlMembersTagOnOff)) + return FALSE; + } + + // This will self-destruct when it goes out of scope. + nsMapiEntryArray dlMembers; + nsMapiEntryArray dlMembersOneOff; + + // Turn IMailUser into IMessage/IPM.Contact. + // Check for magic provider GUID. + struct AbEntryId* abEntryId = (struct AbEntryId*)aEntry.mEntryId; + if (memcmp(abEntryId->provider, CONTAB_PROVIDER_ID, + CONTAB_PROVIDER_ID_LENGTH) != 0) { + PRINTF(("Cannot get to IMessage/IPM.Contact.\n")); + return FALSE; + } + ULONG contactIdLength = abEntryId->length; + LPENTRYID contactId = reinterpret_cast<LPENTRYID>(&(abEntryId->idBytes)); + + ULONG tags[2] = {dlMembersTag, dlMembersTagOnOff}; + nsMapiEntry* values[2]; + ULONG counts[2]; + // We ask for and array one entry larger. + if (!GetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts, true)) { + // If the properties aren't there, the list has no entries so far. + values[0] = new nsMapiEntry[1]; + values[1] = new nsMapiEntry[1]; + counts[0] = counts[1] = 0; + } + dlMembers.mEntries = values[0]; + dlMembersOneOff.mEntries = values[1]; + dlMembers.mNbEntries = counts[0]; + dlMembersOneOff.mNbEntries = counts[1]; + + if (dlMembers.mNbEntries != dlMembersOneOff.mNbEntries) { + PRINTF(("DL members and DL one off members have different length.\n")); + return FALSE; + } + + // Append a new entry at the end. The array is already large enough. + + // Construct a distribution list entry based on a contact. + size_t dlEntryIdLength = sizeof(struct DlEntryId) + contactIdLength; + struct DlEntryId* dlEntryId = (DlEntryId*)moz_xmalloc(dlEntryIdLength); + memset(dlEntryId->flags, 0, DLENTRY_FLAGS_LENGTH); + memcpy(dlEntryId->provider, DL_PROVIDER_ID, DL_PROVIDER_ID_LENGTH); + // See documentation referenced above: 0xC3 = 0x80 | 0x40 | 0x03. + memset(dlEntryId->type, 0xC3, DLENTRY_TYPE_LENGTH); + memcpy(dlEntryId->idBytes, contactId, contactIdLength); + dlMembers.mEntries[dlMembers.mNbEntries].Assign( + dlEntryIdLength, reinterpret_cast<LPENTRYID>(dlEntryId)); + + // Construct a one-off entry. + size_t dlEntryIdOoLength = sizeof(struct DlEntryIdOo) + + 2 * (wcslen(aDisplay) + 4 + wcslen(aEmail) + 3); + struct DlEntryIdOo* dlEntryIdOo = + (DlEntryIdOo*)moz_xmalloc(dlEntryIdOoLength); + memset(dlEntryIdOo->flags, 0, DLENTRY_OO_FLAGS_LENGTH); + memcpy(dlEntryIdOo->provider, DL_OO_PROVIDER_ID, DL_OO_PROVIDER_ID_LENGTH); + dlEntryIdOo->versionAndBits = MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO; + + // Populate the variable part. A bit of stone-age programming ;-) + size_t length = 2 * (wcslen(aDisplay) + 1); + memcpy(dlEntryIdOo->variable, aDisplay, length); + size_t offset = length; + + length = 2 * (4 + 1); + memcpy(dlEntryIdOo->variable + offset, L"SMTP", length); + offset += length; + + length = 2 * (wcslen(aEmail) + 1); + memcpy(dlEntryIdOo->variable + offset, aEmail, length); + + dlMembersOneOff.mEntries[dlMembersOneOff.mNbEntries].Assign( + dlEntryIdOoLength, reinterpret_cast<LPENTRYID>(dlEntryIdOo)); + + free(dlEntryId); + free(dlEntryIdOo); + + dlMembers.mNbEntries++; + dlMembersOneOff.mNbEntries++; + + counts[0] = dlMembers.mNbEntries; + counts[1] = dlMembersOneOff.mNbEntries; + if (!SetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts)) { + PRINTF(("Cannot set DL members.\n")); + 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 = + reinterpret_cast<wchar_t*>(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 %08lx is not a string.\n", aPropertyTag)); + return FALSE; + } + nsMapiEntry nullEntry; + return SetMAPIProperties(nullEntry, aObject, 1, &value, false); +} + +BOOL nsAbWinHelper::SetPropertiesUString(const nsMapiEntry& aDir, + const nsMapiEntry& aObject, + const ULONG aPropertyTags[], + ULONG aNbProperties, + nsString aValues[]) { + LPSPropValue values = new SPropValue[aNbProperties]; + if (!values) return FALSE; + + ULONG currentValue = 0; + nsAutoCString alternativeValue; + BOOL retCode = TRUE; + + for (ULONG i = 0; i < aNbProperties; ++i) { + values[currentValue].ulPropTag = aPropertyTags[i]; + if (PROP_TYPE(aPropertyTags[i]) == PT_UNICODE) { + const wchar_t* value = aValues[i].get(); + values[currentValue++].Value.lpszW = const_cast<wchar_t*>(value); + } else if (PROP_TYPE(aPropertyTags[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(aDir, aObject, currentValue, values, true); + for (ULONG i = 0; i < currentValue; ++i) { + if (PROP_TYPE(aPropertyTags[i]) == PT_STRING8) { + free(values[i].Value.lpszA); + } + } + delete[] values; + return retCode; +} + +BOOL nsAbWinHelper::SetPropertyDate(const nsMapiEntry& aDir, + const nsMapiEntry& aObject, + bool fromContact, 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(aDir, aObject, 1, &value, fromContact); + } + return TRUE; + } + return FALSE; +} + +BOOL nsAbWinHelper::CreateEntryInternal(const nsMapiEntry& aParent, + nsMapiEntry& aNewEntry, + const char* aContactClass, + const wchar_t* aName) { + // We create an IPM.Contact or IPM.DistList message in the contacts folder. + // To find that folder, we look for our `aParent` in the hierarchy table + // and use the matching `PR_CONTAB_FOLDER_ENTRYID` for the folder. + nsMapiInterfaceWrapper<LPABCONT> rootFolder; + nsMapiInterfaceWrapper<LPMAPITABLE> folders; + ULONG objType = 0; + mLastError = mAddressBook->OpenEntry(0, NULL, NULL, 0, &objType, rootFolder); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open root %08lx (creating new entry).\n", mLastError)); + return FALSE; + } + mLastError = rootFolder->GetHierarchyTable(CONVENIENT_DEPTH, folders); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get hierarchy %08lx (creating new entry).\n", mLastError)); + return FALSE; + } + + // Request `PR_ENTRYID` and `PR_CONTAB_FOLDER_ENTRYID`. +#define PR_CONTAB_FOLDER_ENTRYID PROP_TAG(PT_BINARY, 0x6610) + static const SizedSPropTagArray(2, properties) = { + 2, {PR_ENTRYID, PR_CONTAB_FOLDER_ENTRYID}}; + mLastError = folders->SetColumns((LPSPropTagArray)&properties, 0); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set columns %08lx (creating new entry).\n", mLastError)); + return FALSE; + } + + ULONG rowCount = 0; + bool found = false; + nsMapiEntry conTab; + mLastError = folders->GetRowCount(0, &rowCount); + if (HR_SUCCEEDED(mLastError)) { + do { + LPSRowSet rowSet = NULL; + + rowCount = 0; + mLastError = folders->QueryRows(1, 0, &rowSet); + if (HR_SUCCEEDED(mLastError)) { + rowCount = rowSet->cRows; + if (rowCount > 0) { + ULONG result; + // Get entry ID from row and compare. + SPropValue& colValue = rowSet->aRow->lpProps[0]; + + mLastError = mAddressSession->CompareEntryIDs( + aParent.mByteCount, aParent.mEntryId, colValue.Value.bin.cb, + reinterpret_cast<LPENTRYID>(colValue.Value.bin.lpb), 0, &result); + if (HR_FAILED(mLastError)) { + PRINTF(("CompareEntryIDs failed with %08lx (creating new entry).\n", + mLastError)); + } + if (result) { + SPropValue& conTabValue = rowSet->aRow->lpProps[1]; + conTab.Assign( + conTabValue.Value.bin.cb, + reinterpret_cast<LPENTRYID>(conTabValue.Value.bin.lpb)); + found = true; + break; + } + } + MyFreeProws(rowSet); + } else { + PRINTF(("Cannot query rows %08lx (creating new entry).\n", mLastError)); + } + } while (rowCount > 0); + } + if (HR_FAILED(mLastError)) return HR_SUCCEEDED(mLastError); + + if (!found) { + PRINTF(("Cannot find folder for contact in hierarchy table.\n")); + return FALSE; + } + + // Open store and contact folder. + PRINTF(("Found contact folder associated with AB container.\n")); + nsMapiEntry storeEntry; + // Get the entry ID of the related store. This won't work for the + // Global Address List (GAL) since it doesn't provide contacts from a + // local store. + if (!GetPropertyBin(aParent, PR_STORE_ENTRYID, storeEntry)) { + PRINTF(("Cannot get PR_STORE_ENTRYID, likely not a local AB.\n")); + return FALSE; + } + nsMapiInterfaceWrapper<LPMDB> store; + mLastError = mAddressSession->OpenMsgStore( + 0, storeEntry.mByteCount, storeEntry.mEntryId, NULL, 0, store); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open MAPI message store %08lx.\n", mLastError)); + return FALSE; + } + nsMapiInterfaceWrapper<LPMAPIFOLDER> contactFolder; + mLastError = + store->OpenEntry(conTab.mByteCount, conTab.mEntryId, &IID_IMAPIFolder, + MAPI_MODIFY, &objType, contactFolder); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open contact folder %08lx.\n", mLastError)); + return FALSE; + } + + // Crazy as it seems, contacts and distribution lists are stored as message. + nsMapiInterfaceWrapper<LPMESSAGE> newEntry; + mLastError = contactFolder->CreateMessage(&IID_IMessage, 0, newEntry); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot create new entry %08lx.\n", mLastError)); + return FALSE; + } + + SPropValue propValue; + LPSPropProblemArray problems = NULL; + propValue.ulPropTag = PR_MESSAGE_CLASS_A; + propValue.Value.lpszA = const_cast<char*>(aContactClass); + mLastError = newEntry->SetProps(1, &propValue, &problems); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set message class %08lx.\n", mLastError)); + return FALSE; + } + + if (strcmp(aContactClass, "IPM.DistList") == 0) { + // Set distribution list name. + problems = NULL; + GetDlNameTag(newEntry.Get(), propValue.ulPropTag); + propValue.Value.lpszW = const_cast<wchar_t*>(aName); + mLastError = newEntry->SetProps(1, &propValue, &problems); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set DL name %08lx.\n", mLastError)); + return FALSE; + } + } + + mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot commit new entry %08lx.\n", mLastError)); + return FALSE; + } + + // Get the entry ID of the contact (IMessage). + 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 %08lx.\n", mLastError)); + return FALSE; + } + + // Construct the entry ID of the related address book entry (IMailUser). + AbEntryId* abEntryId = + (AbEntryId*)moz_xmalloc(sizeof(AbEntryId) + value->Value.bin.cb); + if (!abEntryId) return FALSE; + memset(abEntryId, 0, 4); // Null out the flags. + memcpy(abEntryId->provider, CONTAB_PROVIDER_ID, CONTAB_PROVIDER_ID_LENGTH); + memcpy(abEntryId->version, ABENTRY_VERSION, ABENTRY_VERSION_LENGTH); + memcpy(abEntryId->type, ABENTRY_TYPE, ABENTRY_TYPE_LENGTH); + abEntryId->index = 0; + abEntryId->length = value->Value.bin.cb; + memcpy(abEntryId->idBytes, value->Value.bin.lpb, abEntryId->length); + + aNewEntry.Assign(sizeof(AbEntryId) + value->Value.bin.cb, + reinterpret_cast<LPENTRYID>(abEntryId)); + FreeBuffer(value); + + // We need to set a display name otherwise MAPI is really unhappy internally. + SPropValue displayName; + displayName.ulPropTag = PR_DISPLAY_NAME_W; + displayName.Value.lpszW = const_cast<wchar_t*>(aName); + nsMapiInterfaceWrapper<LPMAPIPROP> object; + mLastError = + mAddressBook->OpenEntry(aNewEntry.mByteCount, aNewEntry.mEntryId, + &IID_IMAPIProp, MAPI_MODIFY, &objType, object); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open newly created AB entry %08lx.\n", mLastError)); + return FALSE; + } + mLastError = object->SetProps(1, &displayName, &problems); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set display name %08lx.\n", mLastError)); + return FALSE; + } + + return TRUE; +} + +BOOL nsAbWinHelper::CreateEntry(const nsMapiEntry& aParent, + nsMapiEntry& aNewEntry) { + nsAutoString tempName(L"" kDummyDisplayName); + tempName.AppendInt(sEntryCounter++); + return CreateEntryInternal(aParent, aNewEntry, "IPM.Contact", tempName.get()); +} + +BOOL nsAbWinHelper::CreateDistList(const nsMapiEntry& aParent, + nsMapiEntry& aNewEntry, + const wchar_t* aName) { + return CreateEntryInternal(aParent, aNewEntry, "IPM.DistList", aName); +} + +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 %08lx.\n", mLastError)); + return FALSE; + } + // Historic comment: May be relevant in the future. + // WAB removed in bug 1687132. + // 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 %08lx.\n", mLastError)); + return FALSE; + } + if (aRestriction != NULL) { + mLastError = contents->Restrict(aRestriction, 0); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set restriction %08lx.\n", mLastError)); + return FALSE; + } + } + mLastError = contents->SetColumns((LPSPropTagArray)&ContentsColumns, 0); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set columns %08lx.\n", mLastError)); + return FALSE; + } + mLastError = contents->GetRowCount(0, &rowCount); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get result count %08lx.\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 %08lx.\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]; + + // Sometimes Outlooks spits the dummy here :-( + // That is meant to be a byte count and NOT an error code of 0x8004010F. + // We gloss over it. + if (currentValue.Value.bin.cb == (ULONG)MAPI_E_NOT_FOUND || + currentValue.Value.bin.lpb == NULL) { + PRINTF(("Error fetching rows.\n")); + return TRUE; + } + current.Assign(currentValue.Value.bin.cb, + reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb)); + } + ++aNbElements; + } + MyFreeProws(rowSet); + } while (rowCount > 0); + return TRUE; +} + +HRESULT nsAbWinHelper::OpenMAPIObject(const nsMapiEntry& aDir, + const nsMapiEntry& aObject, + bool aFromContact, ULONG aFlags, + LPUNKNOWN* aResult) { + nsMapiEntry storeEntry; + ULONG contactIdLength = 0; + LPENTRYID contactId = NULL; + if (aFromContact) { + // Get the entry ID of the related store. This won't work for the + // Global Address List (GAL) since it doesn't provide contacts from a + // local store. + if (!GetPropertyBin(aDir, PR_STORE_ENTRYID, storeEntry)) { + PRINTF(("Cannot get PR_STORE_ENTRYID, likely not a local AB.\n")); + aFromContact = false; + } + // Check for magic provider GUID. + struct AbEntryId* abEntryId = (struct AbEntryId*)aObject.mEntryId; + if (memcmp(abEntryId->provider, CONTAB_PROVIDER_ID, + CONTAB_PROVIDER_ID_LENGTH) != 0) { + aFromContact = false; + } else { + contactIdLength = abEntryId->length; + contactId = reinterpret_cast<LPENTRYID>(&(abEntryId->idBytes)); + } + } + + ULONG objType = 0; + if (aFromContact) { + // Open the store. + HRESULT retCode; + nsMapiInterfaceWrapper<LPMDB> store; + retCode = mAddressSession->OpenMsgStore( + 0, storeEntry.mByteCount, storeEntry.mEntryId, NULL, 0, store); + if (HR_FAILED(retCode)) { + PRINTF(("Cannot open MAPI message store %08lx.\n", retCode)); + return retCode; + } + // Open the contact object. + retCode = store->OpenEntry(contactIdLength, contactId, &IID_IMessage, 0, + &objType, aResult); + return retCode; + } else { + // Open the address book object. + return mAddressBook->OpenEntry(aObject.mByteCount, aObject.mEntryId, + &IID_IMAPIProp, 0, &objType, aResult); + } +} + +BOOL nsAbWinHelper::GetMAPIProperties(const nsMapiEntry& aDir, + const nsMapiEntry& aObject, + const ULONG aPropertyTags[], + ULONG aNbProperties, LPSPropValue& aValue, + ULONG& aValueCount, bool aFromContact) { + nsMapiInterfaceWrapper<LPMAPIPROP> object; + LPSPropTagArray properties = NULL; + + mLastError = OpenMAPIObject(aDir, aObject, aFromContact, 0, object); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open entry %08lx.\n", mLastError)); + return FALSE; + } + AllocateBuffer(CbNewSPropTagArray(aNbProperties), + reinterpret_cast<void**>(&properties)); + properties->cValues = aNbProperties; + for (ULONG 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 %08lx.\n", mLastError)); + } + return HR_SUCCEEDED(mLastError); +} + +BOOL nsAbWinHelper::SetMAPIProperties(const nsMapiEntry& aDir, + const nsMapiEntry& aObject, + ULONG aNbProperties, + const LPSPropValue& aValues, + bool aFromContact) { + nsMapiInterfaceWrapper<LPMAPIPROP> object; + LPSPropProblemArray problems = NULL; + + mLastError = OpenMAPIObject(aDir, aObject, aFromContact, MAPI_MODIFY, object); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open entry %08lx.\n", mLastError)); + return FALSE; + } + mLastError = object->SetProps(aNbProperties, aValues, &problems); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot update the object %08lx.\n", mLastError)); + return FALSE; + } + if (problems != NULL) { + for (ULONG i = 0; i < problems->cProblem; ++i) { + PRINTF(("Problem %lu: index %lu code %08lx.\n", i, + problems->aProblem[i].ulIndex, problems->aProblem[i].scode)); + } + mAddressFreeBuffer(problems); + } + mLastError = object->SaveChanges(0); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot commit changes %08lx.\n", mLastError)); + } + return HR_SUCCEEDED(mLastError); +} + +BOOL nsAbWinHelper::DeleteMAPIProperties(const nsMapiEntry& aDir, + const nsMapiEntry& aObject, + const LPSPropTagArray aProps, + bool aFromContact) { + nsMapiInterfaceWrapper<LPMAPIPROP> object; + LPSPropProblemArray problems = NULL; + + mLastError = OpenMAPIObject(aDir, aObject, aFromContact, MAPI_MODIFY, object); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open entry %08lx.\n", mLastError)); + return FALSE; + } + mLastError = object->DeleteProps(aProps, &problems); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot update the object (DeleteProps) %08lx.\n", mLastError)); + return FALSE; + } + if (problems != NULL) { + for (ULONG i = 0; i < problems->cProblem; ++i) { + PRINTF(("Problem %lu: index %lu code %08lx.\n", i, + problems->aProblem[i].ulIndex, problems->aProblem[i].scode)); + } + mAddressFreeBuffer(problems); + } + mLastError = object->SaveChanges(0); + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot commit changes %08lx.\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() : mHelper(NULL) { + mHelper = new nsMapiAddressBook; +} + +nsAbWinHelperGuard::~nsAbWinHelperGuard(void) { delete mHelper; } + +void makeEntryIdFromURI(const char* aScheme, const char* aUri, + nsCString& aEntry) { + aEntry.Truncate(); + uint32_t schemeLength = strlen(aScheme); + + if (strncmp(aUri, aScheme, schemeLength) == 0) { + // Assign string from position `schemeLength`. + aEntry = aUri + schemeLength; + + // Now strip the parent directory before the /. + int ind = aEntry.FindChar('/'); + if (ind != kNotFound) { + aEntry = Substring(aEntry, ind + 1); + } + } +} + +bool nsAbWinHelper::CompareEntryIDs(nsCString& aEntryID1, + nsCString& aEntryID2) { + ULONG result; + nsMapiEntry e1; + nsMapiEntry e2; + e1.Assign(aEntryID1); + e2.Assign(aEntryID2); + mLastError = mAddressSession->CompareEntryIDs( + e1.mByteCount, e1.mEntryId, e2.mByteCount, e2.mEntryId, 0, &result); + if (HR_FAILED(mLastError)) { + PRINTF(("CompareEntryIDs failed with %08lx (CompareEntryIDs()).\n", + mLastError)); + return false; + } + return result ? true : false; +} |