1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #define INITGUID
6 #define USES_IID_IMAPIProp
7 #define USES_IID_IMessage
8 #define USES_IID_IMAPIFolder
9 #define USES_IID_IMAPIContainer
10 #define USES_IID_IABContainer
11 #define USES_IID_IMAPITable
12 #define USES_IID_IDistList
13 
14 #include "nsAbWinHelper.h"
15 #include "nsMapiAddressBook.h"
16 
17 #include <mapiguid.h>
18 
19 #include "mozilla/Logging.h"
20 
21 #define PRINT_TO_CONSOLE 0
22 #if PRINT_TO_CONSOLE
23 #  define PRINTF(args) printf args
24 #else
25 static mozilla::LazyLogModule gAbWinHelperLog("AbWinHelper");
26 #  define PRINTF(args) MOZ_LOG(gAbWinHelperLog, mozilla::LogLevel::Debug, args)
27 #endif
28 
29 // Small utility to ensure release of all MAPI interfaces
30 template <class tInterface>
31 struct nsMapiInterfaceWrapper {
32   tInterface mInterface;
33 
nsMapiInterfaceWrappernsMapiInterfaceWrapper34   nsMapiInterfaceWrapper(void) : mInterface(NULL) {}
~nsMapiInterfaceWrappernsMapiInterfaceWrapper35   ~nsMapiInterfaceWrapper(void) {
36     if (mInterface != NULL) {
37       mInterface->Release();
38     }
39   }
operator LPUNKNOWN*nsMapiInterfaceWrapper40   operator LPUNKNOWN*(void) {
41     return reinterpret_cast<LPUNKNOWN*>(&mInterface);
42   }
operator ->nsMapiInterfaceWrapper43   tInterface operator->(void) const { return mInterface; }
operator tInterface*nsMapiInterfaceWrapper44   operator tInterface*(void) { return &mInterface; }
GetnsMapiInterfaceWrapper45   tInterface Get(void) const { return mInterface; }
46 };
47 
assignEntryID(LPENTRYID & aTarget,LPENTRYID aSource,ULONG aByteCount)48 static void assignEntryID(LPENTRYID& aTarget, LPENTRYID aSource,
49                           ULONG aByteCount) {
50   if (aTarget != NULL) {
51     delete[](reinterpret_cast<LPBYTE>(aTarget));
52     aTarget = NULL;
53   }
54   if (aSource != NULL) {
55     aTarget = reinterpret_cast<LPENTRYID>(new BYTE[aByteCount]);
56     memcpy(aTarget, aSource, aByteCount);
57   }
58 }
59 
nsMapiEntry(void)60 nsMapiEntry::nsMapiEntry(void) : mByteCount(0), mEntryId(NULL) {
61   MOZ_COUNT_CTOR(nsMapiEntry);
62 }
63 
nsMapiEntry(ULONG aByteCount,LPENTRYID aEntryId)64 nsMapiEntry::nsMapiEntry(ULONG aByteCount, LPENTRYID aEntryId)
65     : mByteCount(0), mEntryId(NULL) {
66   Assign(aByteCount, aEntryId);
67   MOZ_COUNT_CTOR(nsMapiEntry);
68 }
69 
Move(nsMapiEntry & target,nsMapiEntry & source)70 void nsMapiEntry::Move(nsMapiEntry& target, nsMapiEntry& source) {
71   target.mByteCount = source.mByteCount;
72   target.mEntryId = source.mEntryId;
73   source.mByteCount = 0;
74   source.mEntryId = NULL;
75 }
76 
~nsMapiEntry(void)77 nsMapiEntry::~nsMapiEntry(void) {
78   Assign(0, NULL);
79   MOZ_COUNT_DTOR(nsMapiEntry);
80 }
81 
Assign(ULONG aByteCount,LPENTRYID aEntryId)82 void nsMapiEntry::Assign(ULONG aByteCount, LPENTRYID aEntryId) {
83   assignEntryID(mEntryId, aEntryId, aByteCount);
84   mByteCount = aByteCount;
85 }
86 
Assign(const nsCString & aString)87 void nsMapiEntry::Assign(const nsCString& aString) {
88   Assign(0, NULL);
89   ULONG byteCount = aString.Length() / 2;
90 
91   if ((aString.Length() & 0x01) != 0) {
92     // Something wrong here, we should always get an even number of hex digits.
93     byteCount += 1;
94   }
95   unsigned char* currentTarget = new unsigned char[byteCount];
96 
97   mByteCount = byteCount;
98   mEntryId = reinterpret_cast<LPENTRYID>(currentTarget);
99   ULONG j = 0;
100   for (uint32_t i = 0; i < aString.Length(); i += 2) {
101     char c1 = aString.CharAt(i);
102     char c2 = i + 1 < aString.Length() ? aString.CharAt(i + 1) : '0';
103     // clang-format off
104     currentTarget[j] =
105         ((c1 <= '9' ? c1 - '0' : c1 - 'A' + 10) << 4) |
106          (c2 <= '9' ? c2 - '0' : c2 - 'A' + 10);
107     // clang-format on
108     j++;
109   }
110 }
111 
ToString(nsCString & aString) const112 void nsMapiEntry::ToString(nsCString& aString) const {
113   aString.Truncate();
114   aString.SetCapacity(mByteCount * 2);
115   char twoBytes[3];
116 
117   for (ULONG i = 0; i < mByteCount; i++) {
118     sprintf(twoBytes, "%02X", (reinterpret_cast<unsigned char*>(mEntryId))[i]);
119     aString.Append(twoBytes);
120   }
121 }
122 
Dump(void) const123 void nsMapiEntry::Dump(void) const {
124   PRINTF(("%lu\n", mByteCount));
125   for (ULONG i = 0; i < mByteCount; ++i) {
126     PRINTF(("%02X", (reinterpret_cast<unsigned char*>(mEntryId))[i]));
127   }
128   PRINTF(("\n"));
129 }
130 
nsMapiEntryArray(void)131 nsMapiEntryArray::nsMapiEntryArray(void) : mEntries(NULL), mNbEntries(0) {
132   MOZ_COUNT_CTOR(nsMapiEntryArray);
133 }
134 
~nsMapiEntryArray(void)135 nsMapiEntryArray::~nsMapiEntryArray(void) {
136   if (mEntries) {
137     delete[] mEntries;
138   }
139   MOZ_COUNT_DTOR(nsMapiEntryArray);
140 }
141 
CleanUp(void)142 void nsMapiEntryArray::CleanUp(void) {
143   if (mEntries != NULL) {
144     delete[] mEntries;
145     mEntries = NULL;
146     mNbEntries = 0;
147   }
148 }
149 
150 // Microsoft distinguishes between address book entries and contacts.
151 // Address book entries are of class IMailUser and are stored in containers
152 // of class IABContainer.
153 // Local contacts are stored in the "contacts folder" of class IMAPIFolder and
154 // are of class IMessage with "message class" IPM.Contact.
155 // For local address books the entry ID of the contact can be derived from the
156 // entry ID of the address book entry and vice versa.
157 // Most attributes can be retrieved from both classes with some exceptions:
158 // The primary e-mail address is only stored on the IMailUser, the contact
159 // has three named email properties (which are not used so far).
160 // The birthday is only stored on the contact.
161 // `OpenMAPIObject()` can open the address book entry as well as the contact,
162 // to open the concact it needs to get the message store from via the
163 // address book container (or "directory" in Thunderbird terms).
164 // Apart from Microsoft documentation, the best source of information
165 // is the MAPI programmers mailing list at MAPI-L@PEACH.EASE.LSOFT.COM.
166 // All the information that was needed to "refresh" the MAPI implementation
167 // in Thunderbird was obtained via these threads:
168 // https://peach.ease.lsoft.com/scripts/wa-PEACH.exe?A2=2012&L=MAPI-L&D=0&P=20988415
169 // https://peach.ease.lsoft.com/scripts/wa-PEACH.exe?A2=2101&L=MAPI-L&D=0&P=21034512
170 
171 // Some stuff to access the entry ID of the contact (IMessage, IPM.Contact)
172 // from the address book entry ID (IMailUser).
173 // The address book entry ID has the following structure, see:
174 // https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcdata/c33d5b9c-d044-4727-96e2-2051f8419ab1
175 #define ABENTRY_FLAGS_LENGTH 4
176 #define CONTAB_PROVIDER_ID \
177   "\xFE\x42\xAA\x0A\x18\xC7\x1A\x10\xE8\x85\x0B\x65\x1C\x24\x00\x00"
178 #define CONTAB_PROVIDER_ID_LENGTH 16
179 #define ABENTRY_VERSION "\x03\x00\x00\x00"
180 #define ABENTRY_VERSION_LENGTH 4
181 #define ABENTRY_TYPE "\x04\x00\x00\x00"
182 #define ABENTRY_TYPE_LENGTH 4
183 
184 struct AbEntryId {
185   BYTE flags[ABENTRY_FLAGS_LENGTH];
186   BYTE provider[CONTAB_PROVIDER_ID_LENGTH];
187   BYTE version[ABENTRY_VERSION_LENGTH];
188   BYTE type[ABENTRY_TYPE_LENGTH];
189   ULONG index;
190   ULONG length;
191   BYTE idBytes[];
192 };
193 
194 // Some stuff to access the entry IDs of members in a distribution list
195 // (IMessage, IPM.DistList):
196 // https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxocntc/02656215-1cb0-4b06-a077-b07e756216be
197 // Also handy the reference to the so-called "one off" members:
198 // https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcdata/b32d23af-85f6-4e92-8387-53a1950ae7ba
199 #define DLENTRY_FLAGS_LENGTH 4
200 #define DL_PROVIDER_ID \
201   "\xC0\x91\xAD\xD3\x51\x9D\xCF\x11\xA4\xA9\x00\xAA\x00\x47\xFA\xA4"
202 #define DL_PROVIDER_ID_LENGTH 16
203 #define DLENTRY_TYPE_LENGTH 1
204 struct DlEntryId {
205   BYTE flags[DLENTRY_FLAGS_LENGTH];
206   BYTE provider[DL_PROVIDER_ID_LENGTH];
207   BYTE type[DLENTRY_TYPE_LENGTH];
208   BYTE idBytes[];
209 };
210 
211 #define DLENTRY_OO_FLAGS_LENGTH 4
212 #define DL_OO_PROVIDER_ID \
213   "\x81\x2B\x1F\xA4\xBE\xA3\x10\x19\x9D\x6E\x00\xDD\x01\x0F\x54\x02"
214 #define DL_OO_PROVIDER_ID_LENGTH 16
215 struct DlEntryIdOo {
216   BYTE flags[DLENTRY_OO_FLAGS_LENGTH];
217   BYTE provider[DL_OO_PROVIDER_ID_LENGTH];
218   // Note that the documentation specifies a two-byte version followed by a
219   // two-byte "bit collection", but MFCMapi
220   // (https://github.com/stephenegriffin/mfcmapi) shows, for example:
221   // dwBitmask: 0x80010000 = MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO.
222   // Intel x86 and AMD64 / x86-64 hardware is little-endian, so that
223   // equates to 0x0000 0x01 0x80 in memory:
224   // M (1 bit): (mask 0x0100) (MIME) and U (1 bit): (mask 0x0080) (Unicode).
225   ULONG versionAndBits;
226   BYTE variable[];
227 };
228 
229 using namespace mozilla;
230 
231 static nsMapiEntry nullEntry;
232 
233 uint32_t nsAbWinHelper::sEntryCounter = 0;
234 mozilla::StaticMutex nsAbWinHelper::sMutex;
235 // There seems to be a deadlock/auto-destruction issue
236 // in MAPI when multiple threads perform init/release
237 // operations at the same time. So I've put a mutex
238 // around both the initialize process and the destruction
239 // one. I just hope the rest of the calls don't need the
240 // same protection (MAPI is supposed to be thread-safe).
241 
nsAbWinHelper(void)242 nsAbWinHelper::nsAbWinHelper(void) : mLastError(S_OK), mAddressBook(NULL) {
243   MOZ_COUNT_CTOR(nsAbWinHelper);
244 }
245 
~nsAbWinHelper(void)246 nsAbWinHelper::~nsAbWinHelper(void) { MOZ_COUNT_DTOR(nsAbWinHelper); }
247 
GetFolders(nsMapiEntryArray & aFolders)248 BOOL nsAbWinHelper::GetFolders(nsMapiEntryArray& aFolders) {
249   aFolders.CleanUp();
250   nsMapiInterfaceWrapper<LPABCONT> rootFolder;
251   nsMapiInterfaceWrapper<LPMAPITABLE> folders;
252   ULONG objType = 0;
253   ULONG rowCount = 0;
254   SRestriction restriction;
255   SPropTagArray folderColumns;
256 
257   mLastError = mAddressBook->OpenEntry(0, NULL, NULL, 0, &objType, rootFolder);
258   if (HR_FAILED(mLastError)) {
259     PRINTF(("Cannot open root %08lx.\n", mLastError));
260     return FALSE;
261   }
262   mLastError = rootFolder->GetHierarchyTable(0, folders);
263   if (HR_FAILED(mLastError)) {
264     PRINTF(("Cannot get hierarchy %08lx.\n", mLastError));
265     return FALSE;
266   }
267   // We only take into account modifiable containers,
268   // otherwise, we end up with all the directory services...
269   restriction.rt = RES_BITMASK;
270   restriction.res.resBitMask.ulPropTag = PR_CONTAINER_FLAGS;
271   restriction.res.resBitMask.relBMR = BMR_NEZ;
272   restriction.res.resBitMask.ulMask = AB_MODIFIABLE;
273   mLastError = folders->Restrict(&restriction, 0);
274   if (HR_FAILED(mLastError)) {
275     PRINTF(("Cannot restrict table %08lx.\n", mLastError));
276   }
277   folderColumns.cValues = 1;
278   folderColumns.aulPropTag[0] = PR_ENTRYID;
279   mLastError = folders->SetColumns(&folderColumns, 0);
280   if (HR_FAILED(mLastError)) {
281     PRINTF(("Cannot set columns %08lx.\n", mLastError));
282     return FALSE;
283   }
284   mLastError = folders->GetRowCount(0, &rowCount);
285   if (HR_SUCCEEDED(mLastError)) {
286     aFolders.mEntries = new nsMapiEntry[rowCount];
287     aFolders.mNbEntries = 0;
288     do {
289       LPSRowSet rowSet = NULL;
290 
291       rowCount = 0;
292       mLastError = folders->QueryRows(1, 0, &rowSet);
293       if (HR_SUCCEEDED(mLastError)) {
294         rowCount = rowSet->cRows;
295         if (rowCount > 0) {
296           nsMapiEntry& current = aFolders.mEntries[aFolders.mNbEntries++];
297           SPropValue& currentValue = rowSet->aRow->lpProps[0];
298 
299           current.Assign(
300               currentValue.Value.bin.cb,
301               reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb));
302         }
303         MyFreeProws(rowSet);
304       } else {
305         PRINTF(("Cannot query rows %08lx.\n", mLastError));
306       }
307     } while (rowCount > 0);
308   }
309   return HR_SUCCEEDED(mLastError);
310 }
311 
GetCards(const nsMapiEntry & aParent,LPSRestriction aRestriction,nsMapiEntryArray & aCards)312 BOOL nsAbWinHelper::GetCards(const nsMapiEntry& aParent,
313                              LPSRestriction aRestriction,
314                              nsMapiEntryArray& aCards) {
315   aCards.CleanUp();
316   return GetContents(aParent, aRestriction, &aCards.mEntries, aCards.mNbEntries,
317                      0);
318 }
319 
GetNodes(const nsMapiEntry & aParent,nsMapiEntryArray & aNodes)320 BOOL nsAbWinHelper::GetNodes(const nsMapiEntry& aParent,
321                              nsMapiEntryArray& aNodes) {
322   aNodes.CleanUp();
323   return GetContents(aParent, NULL, &aNodes.mEntries, aNodes.mNbEntries,
324                      MAPI_DISTLIST);
325 }
326 
GetCardsCount(const nsMapiEntry & aParent,ULONG & aNbCards)327 BOOL nsAbWinHelper::GetCardsCount(const nsMapiEntry& aParent, ULONG& aNbCards) {
328   aNbCards = 0;
329   return GetContents(aParent, NULL, NULL, aNbCards, 0);
330 }
331 
GetPropertyString(const nsMapiEntry & aObject,ULONG aPropertyTag,nsCString & aName)332 BOOL nsAbWinHelper::GetPropertyString(const nsMapiEntry& aObject,
333                                       ULONG aPropertyTag, nsCString& aName) {
334   aName.Truncate();
335   LPSPropValue values = NULL;
336   ULONG valueCount = 0;
337 
338   if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values,
339                          valueCount)) {
340     return FALSE;
341   }
342 
343   if (valueCount != 1 || values == NULL) {
344     PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyString"));
345     return FALSE;
346   }
347 
348   BOOL success = TRUE;
349   if (PROP_TYPE(values->ulPropTag) == PT_STRING8) {
350     aName = values->Value.lpszA;
351   } else if (PROP_TYPE(values->ulPropTag) == PT_UNICODE) {
352     aName = NS_LossyConvertUTF16toASCII(values->Value.lpszW);
353   } else {
354     PRINTF(("Unexpected return value for property %08lx (x0A is PT_ERROR).\n",
355             values->ulPropTag));
356     success = FALSE;
357   }
358   FreeBuffer(values);
359   return success;
360 }
361 
GetPropertyUString(const nsMapiEntry & aObject,ULONG aPropertyTag,nsString & aName)362 BOOL nsAbWinHelper::GetPropertyUString(const nsMapiEntry& aObject,
363                                        ULONG aPropertyTag, nsString& aName) {
364   aName.Truncate();
365   LPSPropValue values = NULL;
366   ULONG valueCount = 0;
367 
368   if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values,
369                          valueCount)) {
370     return FALSE;
371   }
372   if (valueCount != 1 || values == NULL) {
373     PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyUString"));
374     return FALSE;
375   }
376 
377   BOOL success = TRUE;
378   if (PROP_TYPE(values->ulPropTag) == PT_UNICODE) {
379     aName = values->Value.lpszW;
380   } else if (PROP_TYPE(values->ulPropTag) == PT_STRING8) {
381     aName.AssignASCII(values->Value.lpszA);
382   } else {
383     PRINTF(("Unexpected return value for property %08lx (x0A is PT_ERROR).\n",
384             values->ulPropTag));
385     success = FALSE;
386   }
387   return success;
388 }
389 
GetPropertiesUString(const nsMapiEntry & aDir,const nsMapiEntry & aObject,const ULONG aPropertyTags[],ULONG aNbProperties,nsString aNames[],bool aSuccess[])390 BOOL nsAbWinHelper::GetPropertiesUString(const nsMapiEntry& aDir,
391                                          const nsMapiEntry& aObject,
392                                          const ULONG aPropertyTags[],
393                                          ULONG aNbProperties, nsString aNames[],
394                                          bool aSuccess[]) {
395   LPSPropValue values = NULL;
396   ULONG valueCount = 0;
397 
398   if (!GetMAPIProperties(aDir, aObject, aPropertyTags, aNbProperties, values,
399                          valueCount, true))
400     return FALSE;
401 
402   if (valueCount != aNbProperties || values == NULL) {
403     PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertiesUString"));
404     return FALSE;
405   }
406   for (ULONG i = 0; i < valueCount; ++i) {
407     aNames[i].Truncate();
408     aSuccess[i] = false;
409     if (PROP_ID(values[i].ulPropTag) == PROP_ID(aPropertyTags[i])) {
410       if (PROP_TYPE(values[i].ulPropTag) == PT_STRING8) {
411         aNames[i].AssignASCII(values[i].Value.lpszA);
412         aSuccess[i] = true;
413       } else if (PROP_TYPE(values[i].ulPropTag) == PT_UNICODE) {
414         aNames[i] = values[i].Value.lpszW;
415         aSuccess[i] = true;
416       } else {
417         PRINTF(
418             ("Unexpected return value for property %08lx (x0A is PT_ERROR).\n",
419              values[i].ulPropTag));
420       }
421     }
422   }
423   FreeBuffer(values);
424   return TRUE;
425 }
426 
GetPropertyDate(const nsMapiEntry & aDir,const nsMapiEntry & aObject,bool fromContact,ULONG aPropertyTag,WORD & aYear,WORD & aMonth,WORD & aDay)427 BOOL nsAbWinHelper::GetPropertyDate(const nsMapiEntry& aDir,
428                                     const nsMapiEntry& aObject,
429                                     bool fromContact, ULONG aPropertyTag,
430                                     WORD& aYear, WORD& aMonth, WORD& aDay) {
431   aYear = 0;
432   aMonth = 0;
433   aDay = 0;
434   LPSPropValue values = NULL;
435   ULONG valueCount = 0;
436 
437   if (!GetMAPIProperties(aDir, aObject, &aPropertyTag, 1, values, valueCount,
438                          fromContact)) {
439     return FALSE;
440   }
441   if (valueCount != 1 || values == NULL) {
442     PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyDate"));
443     return FALSE;
444   }
445 
446   BOOL success = TRUE;
447   if (PROP_TYPE(values->ulPropTag) == PT_SYSTIME) {
448     SYSTEMTIME readableTime;
449     if (FileTimeToSystemTime(&values->Value.ft, &readableTime)) {
450       aYear = readableTime.wYear;
451       aMonth = readableTime.wMonth;
452       aDay = readableTime.wDay;
453     }
454   } else {
455     PRINTF(("Cannot retrieve PT_SYSTIME property %08lx (x0A is PT_ERROR).\n",
456             values->ulPropTag));
457     success = FALSE;
458   }
459   FreeBuffer(values);
460   return success;
461 }
462 
GetPropertyLong(const nsMapiEntry & aObject,ULONG aPropertyTag,ULONG & aValue)463 BOOL nsAbWinHelper::GetPropertyLong(const nsMapiEntry& aObject,
464                                     ULONG aPropertyTag, ULONG& aValue) {
465   aValue = 0;
466   LPSPropValue values = NULL;
467   ULONG valueCount = 0;
468 
469   if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values,
470                          valueCount)) {
471     return FALSE;
472   }
473   if (valueCount != 1 || values == NULL) {
474     PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyLong"));
475     return FALSE;
476   }
477 
478   BOOL success = TRUE;
479   if (PROP_TYPE(values->ulPropTag) == PT_LONG) {
480     aValue = values->Value.ul;
481   } else {
482     PRINTF(("Cannot retrieve PT_LONG property %08lx (x0A is PT_ERROR).\n",
483             values->ulPropTag));
484     success = FALSE;
485   }
486   FreeBuffer(values);
487   return success;
488 }
489 
GetPropertyBin(const nsMapiEntry & aObject,ULONG aPropertyTag,nsMapiEntry & aValue)490 BOOL nsAbWinHelper::GetPropertyBin(const nsMapiEntry& aObject,
491                                    ULONG aPropertyTag, nsMapiEntry& aValue) {
492   aValue.Assign(0, NULL);
493   LPSPropValue values = NULL;
494   ULONG valueCount = 0;
495 
496   if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values,
497                          valueCount)) {
498     return FALSE;
499   }
500   if (valueCount != 1 || values == NULL) {
501     PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyBin"));
502     return FALSE;
503   }
504 
505   BOOL success = TRUE;
506   if (PROP_TYPE(values->ulPropTag) == PT_BINARY) {
507     aValue.Assign(values->Value.bin.cb,
508                   reinterpret_cast<LPENTRYID>(values->Value.bin.lpb));
509   } else {
510     PRINTF(("Cannot retrieve PT_BINARY property %08lx (x0A is PT_ERROR).\n",
511             values->ulPropTag));
512     success = FALSE;
513   }
514 
515   FreeBuffer(values);
516   return success;
517 }
518 
GetPropertiesMVBin(const nsMapiEntry & aDir,const nsMapiEntry & aObject,const ULONG aPropertyTags[],ULONG aNbProperties,nsMapiEntry * aEntryIDs[],ULONG aNbElements[],bool aAllocateMore)519 BOOL nsAbWinHelper::GetPropertiesMVBin(
520     const nsMapiEntry& aDir, const nsMapiEntry& aObject,
521     const ULONG aPropertyTags[], ULONG aNbProperties, nsMapiEntry* aEntryIDs[],
522     ULONG aNbElements[], bool aAllocateMore) {
523   LPSPropValue values = NULL;
524   ULONG valueCount = 0;
525 
526   // Initialise output arrays.
527   for (ULONG i = 0; i < aNbProperties; i++) {
528     aEntryIDs[i] = NULL;
529     aNbElements[i] = 0;
530   }
531 
532   if (!GetMAPIProperties(aDir, aObject, aPropertyTags, aNbProperties, values,
533                          valueCount, true)) {
534     return FALSE;
535   }
536   if (valueCount != aNbProperties || values == NULL) {
537     PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyMVBin"));
538     return FALSE;
539   }
540 
541   BOOL success = TRUE;
542   for (ULONG i = 0; i < valueCount; i++) {
543     if (PROP_TYPE(values[i].ulPropTag) == PT_MV_BINARY) {
544       ULONG count = values[i].Value.MVbin.cValues;
545       PRINTF(("Found %lu members in DL.\n", count));
546       aEntryIDs[i] = new nsMapiEntry[aAllocateMore ? count + 1 : count];
547       aNbElements[i] = count;
548       SBinary* currentValue = values[i].Value.MVbin.lpbin;
549       for (ULONG j = 0; j < count; j++) {
550         nsMapiEntry& current = aEntryIDs[i][j];
551         current.Assign(currentValue->cb,
552                        reinterpret_cast<LPENTRYID>(currentValue->lpb));
553         currentValue++;
554       }
555     } else {
556       PRINTF(
557           ("Cannot retrieve PT_MV_BINARY property %08lx (x0A is PT_ERROR).\n",
558            values[i].ulPropTag));
559       success = FALSE;
560     }
561   }
562 
563   FreeBuffer(values);
564   if (!success) {
565     for (ULONG i = 0; i < aNbProperties; i++) {
566       if (aNbElements[i] > 0) delete[] aEntryIDs[i];
567       aEntryIDs[i] = NULL;
568       aNbElements[i] = 0;
569     }
570   }
571   return success;
572 }
573 
SetPropertiesMVBin(const nsMapiEntry & aDir,const nsMapiEntry & aObject,const ULONG aPropertyTags[],ULONG aNbProperties,nsMapiEntry * aEntryIDs[],ULONG aNbElements[])574 BOOL nsAbWinHelper::SetPropertiesMVBin(const nsMapiEntry& aDir,
575                                        const nsMapiEntry& aObject,
576                                        const ULONG aPropertyTags[],
577                                        ULONG aNbProperties,
578                                        nsMapiEntry* aEntryIDs[],
579                                        ULONG aNbElements[]) {
580   LPSPropValue values = new SPropValue[aNbProperties];
581   if (!values) return FALSE;
582 
583   for (ULONG i = 0; i < aNbProperties; i++) {
584     values[i].ulPropTag = aPropertyTags[i];
585     values[i].Value.MVbin.cValues = aNbElements[i];
586     values[i].Value.MVbin.lpbin = new SBinary[aNbElements[i]];
587 
588     SBinary* currentValue = values[i].Value.MVbin.lpbin;
589     for (ULONG j = 0; j < aNbElements[i]; j++) {
590       currentValue->cb = aEntryIDs[i][j].mByteCount;
591       currentValue->lpb = reinterpret_cast<LPBYTE>(aEntryIDs[i][j].mEntryId);
592       currentValue++;
593     }
594   }
595   BOOL retCode = SetMAPIProperties(aDir, aObject, aNbProperties, values, true);
596   for (ULONG i = 0; i < aNbProperties; i++) {
597     delete[] values[i].Value.MVbin.lpbin;
598   }
599   delete[] values;
600   return retCode;
601 }
602 
603 // This function, supposedly indicating whether a particular entry was
604 // in a particular container, doesn't seem to work very well (has
605 // a tendency to return TRUE even if we're talking to different containers...).
TestOpenEntry(const nsMapiEntry & aContainer,const nsMapiEntry & aEntry)606 BOOL nsAbWinHelper::TestOpenEntry(const nsMapiEntry& aContainer,
607                                   const nsMapiEntry& aEntry) {
608   nsMapiInterfaceWrapper<LPMAPICONTAINER> container;
609   nsMapiInterfaceWrapper<LPMAPIPROP> subObject;
610   ULONG objType = 0;
611 
612   mLastError =
613       mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId,
614                               &IID_IMAPIContainer, 0, &objType, container);
615   if (HR_FAILED(mLastError)) {
616     PRINTF(("Cannot open container %08lx.\n", mLastError));
617     return FALSE;
618   }
619   mLastError = container->OpenEntry(aEntry.mByteCount, aEntry.mEntryId, NULL, 0,
620                                     &objType, subObject);
621   return HR_SUCCEEDED(mLastError);
622 }
623 
DeleteEntry(const nsMapiEntry & aContainer,const nsMapiEntry & aEntry)624 BOOL nsAbWinHelper::DeleteEntry(const nsMapiEntry& aContainer,
625                                 const nsMapiEntry& aEntry) {
626   nsMapiInterfaceWrapper<LPABCONT> container;
627   ULONG objType = 0;
628   SBinary entry;
629   SBinaryArray entryArray;
630 
631   mLastError = mAddressBook->OpenEntry(aContainer.mByteCount,
632                                        aContainer.mEntryId, &IID_IABContainer,
633                                        MAPI_MODIFY, &objType, container);
634   if (HR_FAILED(mLastError)) {
635     PRINTF(("Cannot open container %08lx.\n", mLastError));
636     return FALSE;
637   }
638   entry.cb = aEntry.mByteCount;
639   entry.lpb = reinterpret_cast<LPBYTE>(aEntry.mEntryId);
640   entryArray.cValues = 1;
641   entryArray.lpbin = &entry;
642   mLastError = container->DeleteEntries(&entryArray, 0);
643   if (HR_FAILED(mLastError)) {
644     PRINTF(("Cannot delete entry %08lx.\n", mLastError));
645     return FALSE;
646   }
647   return TRUE;
648 }
649 
GetDlMembersTag(IMAPIProp * aMsg,ULONG & aDlMembersTag,ULONG & aDlMembersTagOneOff)650 BOOL nsAbWinHelper::GetDlMembersTag(IMAPIProp* aMsg, ULONG& aDlMembersTag,
651                                     ULONG& aDlMembersTagOneOff) {
652   const GUID guid = {0x00062004,
653                      0x0000,
654                      0x0000,
655                      {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
656   MAPINAMEID nameID;
657   nameID.lpguid = (GUID*)&guid;
658   nameID.ulKind = MNID_ID;
659   LPSPropTagArray lppPropTags;
660   LPMAPINAMEID lpNameID[1] = {&nameID};
661 
662   // Strangely requesting two tags at the same time doesn't appear to work,
663   // so request them separately.
664   // One should be able to set up `lpNameID` with two entries and get two
665   // tags returned in `lppPropTags`, but sadly the second one is always 0.
666   nameID.Kind.lID = 0x8055;  // PidLidDistributionListMembers
667   mLastError = aMsg->GetIDsFromNames(1, lpNameID, 0, &lppPropTags);
668   if (HR_FAILED(mLastError)) {
669     PRINTF(("Cannot get DL prop tag %08lx.\n", mLastError));
670     return FALSE;
671   }
672   aDlMembersTag = lppPropTags[0].aulPropTag[0] | PT_MV_BINARY;
673   mAddressFreeBuffer(lppPropTags);
674 
675   nameID.Kind.lID = 0x8054;  // PidLidDistributionListOneOffMembers
676   mLastError = aMsg->GetIDsFromNames(1, lpNameID, 0, &lppPropTags);
677   if (HR_FAILED(mLastError)) {
678     PRINTF(("Cannot open DL prop tag (one off) %08lx.\n", mLastError));
679     return FALSE;
680   }
681   aDlMembersTagOneOff = lppPropTags[0].aulPropTag[0] | PT_MV_BINARY;
682   mAddressFreeBuffer(lppPropTags);
683 
684   return TRUE;
685 }
686 
GetDlNameTag(IMAPIProp * aMsg,ULONG & aDlNameTag)687 BOOL nsAbWinHelper::GetDlNameTag(IMAPIProp* aMsg, ULONG& aDlNameTag) {
688   const GUID guid = {0x00062004,
689                      0x0000,
690                      0x0000,
691                      {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
692   MAPINAMEID nameID;
693   nameID.lpguid = (GUID*)&guid;
694   nameID.ulKind = MNID_ID;
695   LPSPropTagArray lppPropTags;
696   LPMAPINAMEID lpNameID[1] = {&nameID};
697 
698   nameID.Kind.lID = 0x8053;  // PidLidDistributionListName
699   mLastError = aMsg->GetIDsFromNames(1, lpNameID, 0, &lppPropTags);
700   if (HR_FAILED(mLastError)) {
701     PRINTF(("Cannot get DL prop tag %08lx.\n", mLastError));
702     return FALSE;
703   }
704   aDlNameTag = lppPropTags[0].aulPropTag[0] | PT_UNICODE;
705   mAddressFreeBuffer(lppPropTags);
706 
707   return TRUE;
708 }
709 
DeleteEntryfromDL(const nsMapiEntry & aTopDir,const nsMapiEntry & aDistList,const nsMapiEntry & aEntry)710 BOOL nsAbWinHelper::DeleteEntryfromDL(const nsMapiEntry& aTopDir,
711                                       const nsMapiEntry& aDistList,
712                                       const nsMapiEntry& aEntry) {
713   // First we need to open the distribution list to get the property tag.
714   ULONG dlMembersTag = 0;
715   ULONG dlMembersTagOnOff = 0;
716   {
717     // We do this in a block is `msg` going out of scope will release the
718     // object.
719     nsMapiInterfaceWrapper<LPMAPIPROP> msg;
720     mLastError = OpenMAPIObject(aTopDir, aDistList, true, 0, msg);
721     if (HR_FAILED(mLastError)) {
722       PRINTF(("Cannot open DL entry %08lx.\n", mLastError));
723       return FALSE;
724     }
725     if (!GetDlMembersTag(msg.Get(), dlMembersTag, dlMembersTagOnOff))
726       return FALSE;
727   }
728 
729   // This will self-destruct when it goes out of scope.
730   nsMapiEntryArray dlMembers;
731   nsMapiEntryArray dlMembersOneOff;
732 
733   // Turn IMailUser into IMessage/IPM.Contact.
734   // Check for magic provider GUID.
735   struct AbEntryId* abEntryId = (struct AbEntryId*)aEntry.mEntryId;
736   if (memcmp(abEntryId->provider, CONTAB_PROVIDER_ID,
737              CONTAB_PROVIDER_ID_LENGTH) != 0) {
738     PRINTF(("Cannot get to IMessage/IPM.Contact.\n"));
739     return FALSE;
740   }
741   ULONG contactIdLength = abEntryId->length;
742   LPENTRYID contactId = reinterpret_cast<LPENTRYID>(&(abEntryId->idBytes));
743 
744   ULONG tags[2] = {dlMembersTag, dlMembersTagOnOff};
745   nsMapiEntry* values[2];
746   ULONG counts[2];
747   if (!GetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts)) {
748     PRINTF(("Cannot get DL members.\n"));
749     return FALSE;
750   }
751   dlMembers.mEntries = values[0];
752   dlMembersOneOff.mEntries = values[1];
753   dlMembers.mNbEntries = counts[0];
754   dlMembersOneOff.mNbEntries = counts[1];
755 
756   if (dlMembers.mNbEntries == 0) return FALSE;
757   if (dlMembers.mNbEntries != dlMembersOneOff.mNbEntries) {
758     PRINTF(("DL members and DL one off members have different length.\n"));
759     return FALSE;
760   }
761 
762   ULONG result;
763   for (ULONG i = 0; i < dlMembers.mNbEntries; i++) {
764     struct DlEntryId* dlEntryId =
765         (struct DlEntryId*)dlMembers.mEntries[i].mEntryId;
766     if (memcmp(dlEntryId->provider, DL_PROVIDER_ID, DL_PROVIDER_ID_LENGTH) != 0)
767       continue;
768     mLastError = mAddressSession->CompareEntryIDs(
769         contactIdLength, contactId,
770         dlMembers.mEntries[i].mByteCount - sizeof(struct DlEntryId),
771         reinterpret_cast<LPENTRYID>(dlEntryId->idBytes), 0, &result);
772     if (HR_FAILED(mLastError)) {
773       PRINTF(("CompareEntryIDs failed with %08lx (DeleteEntryfromDL()).\n",
774               mLastError));
775     }
776     if (result) {
777       PRINTF(("Found card to be deleted at position %lu.\n", i));
778 
779       // Kill/free entry and shuffle remaining cards down.
780       dlMembers.mEntries[i].Assign(0, NULL);
781       dlMembersOneOff.mEntries[i].Assign(0, NULL);
782       for (ULONG j = i + 1; j < dlMembers.mNbEntries; j++) {
783         nsMapiEntry::Move(dlMembers.mEntries[j - 1], dlMembers.mEntries[j]);
784         nsMapiEntry::Move(dlMembersOneOff.mEntries[j - 1],
785                           dlMembersOneOff.mEntries[j]);
786       }
787       dlMembers.mNbEntries--;
788       dlMembersOneOff.mNbEntries--;
789 
790       counts[0] = dlMembers.mNbEntries;
791       counts[1] = dlMembersOneOff.mNbEntries;
792       if (counts[0] >= 1) {
793         if (!SetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts)) {
794           PRINTF(("Cannot set DL members.\n"));
795           return FALSE;
796         }
797       } else {
798         static const SizedSPropTagArray(2, properties) = {
799             2, {dlMembersTag, dlMembersTagOnOff}};
800         if (!DeleteMAPIProperties(aTopDir, aDistList,
801                                   (LPSPropTagArray)&properties, true)) {
802           PRINTF(("Cannot delete DL members.\n"));
803           return FALSE;
804         }
805       }
806       return TRUE;
807     }
808   }
809   return FALSE;
810 }
811 
AddEntryToDL(const nsMapiEntry & aTopDir,const nsMapiEntry & aDistList,const nsMapiEntry & aEntry,const wchar_t * aDisplay,const wchar_t * aEmail)812 BOOL nsAbWinHelper::AddEntryToDL(const nsMapiEntry& aTopDir,
813                                  const nsMapiEntry& aDistList,
814                                  const nsMapiEntry& aEntry,
815                                  const wchar_t* aDisplay,
816                                  const wchar_t* aEmail) {
817   // First we need to open the distribution list to get the property tag.
818   ULONG dlMembersTag = 0;
819   ULONG dlMembersTagOnOff = 0;
820   {
821     // We do this in a block is `msg` going out of scope will release the
822     // object.
823     nsMapiInterfaceWrapper<LPMAPIPROP> msg;
824     mLastError = OpenMAPIObject(aTopDir, aDistList, true, 0, msg);
825     if (HR_FAILED(mLastError)) {
826       PRINTF(("Cannot open DL entry %08lx.\n", mLastError));
827       return FALSE;
828     }
829     if (!GetDlMembersTag(msg.Get(), dlMembersTag, dlMembersTagOnOff))
830       return FALSE;
831   }
832 
833   // This will self-destruct when it goes out of scope.
834   nsMapiEntryArray dlMembers;
835   nsMapiEntryArray dlMembersOneOff;
836 
837   // Turn IMailUser into IMessage/IPM.Contact.
838   // Check for magic provider GUID.
839   struct AbEntryId* abEntryId = (struct AbEntryId*)aEntry.mEntryId;
840   if (memcmp(abEntryId->provider, CONTAB_PROVIDER_ID,
841              CONTAB_PROVIDER_ID_LENGTH) != 0) {
842     PRINTF(("Cannot get to IMessage/IPM.Contact.\n"));
843     return FALSE;
844   }
845   ULONG contactIdLength = abEntryId->length;
846   LPENTRYID contactId = reinterpret_cast<LPENTRYID>(&(abEntryId->idBytes));
847 
848   ULONG tags[2] = {dlMembersTag, dlMembersTagOnOff};
849   nsMapiEntry* values[2];
850   ULONG counts[2];
851   // We ask for and array one entry larger.
852   if (!GetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts, true)) {
853     // If the properties aren't there, the list has no entries so far.
854     values[0] = new nsMapiEntry[1];
855     values[1] = new nsMapiEntry[1];
856     counts[0] = counts[1] = 0;
857   }
858   dlMembers.mEntries = values[0];
859   dlMembersOneOff.mEntries = values[1];
860   dlMembers.mNbEntries = counts[0];
861   dlMembersOneOff.mNbEntries = counts[1];
862 
863   if (dlMembers.mNbEntries != dlMembersOneOff.mNbEntries) {
864     PRINTF(("DL members and DL one off members have different length.\n"));
865     return FALSE;
866   }
867 
868   // Append a new entry at the end. The array is already large enough.
869 
870   // Construct a distribution list entry based on a contact.
871   size_t dlEntryIdLength = sizeof(struct DlEntryId) + contactIdLength;
872   struct DlEntryId* dlEntryId = (DlEntryId*)moz_xmalloc(dlEntryIdLength);
873   memset(dlEntryId->flags, 0, DLENTRY_FLAGS_LENGTH);
874   memcpy(dlEntryId->provider, DL_PROVIDER_ID, DL_PROVIDER_ID_LENGTH);
875   // See documentation referenced above: 0xC3 = 0x80 | 0x40 | 0x03.
876   memset(dlEntryId->type, 0xC3, DLENTRY_TYPE_LENGTH);
877   memcpy(dlEntryId->idBytes, contactId, contactIdLength);
878   dlMembers.mEntries[dlMembers.mNbEntries].Assign(
879       dlEntryIdLength, reinterpret_cast<LPENTRYID>(dlEntryId));
880 
881   // Construct a one-off entry.
882   size_t dlEntryIdOoLength = sizeof(struct DlEntryIdOo) +
883                              2 * (wcslen(aDisplay) + 4 + wcslen(aEmail) + 3);
884   struct DlEntryIdOo* dlEntryIdOo =
885       (DlEntryIdOo*)moz_xmalloc(dlEntryIdOoLength);
886   memset(dlEntryIdOo->flags, 0, DLENTRY_OO_FLAGS_LENGTH);
887   memcpy(dlEntryIdOo->provider, DL_OO_PROVIDER_ID, DL_OO_PROVIDER_ID_LENGTH);
888   dlEntryIdOo->versionAndBits = MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO;
889 
890   // Populate the variable part. A bit of stone-age programming ;-)
891   size_t length = 2 * (wcslen(aDisplay) + 1);
892   memcpy(dlEntryIdOo->variable, aDisplay, length);
893   size_t offset = length;
894 
895   length = 2 * (4 + 1);
896   memcpy(dlEntryIdOo->variable + offset, L"SMTP", length);
897   offset += length;
898 
899   length = 2 * (wcslen(aEmail) + 1);
900   memcpy(dlEntryIdOo->variable + offset, aEmail, length);
901 
902   dlMembersOneOff.mEntries[dlMembersOneOff.mNbEntries].Assign(
903       dlEntryIdOoLength, reinterpret_cast<LPENTRYID>(dlEntryIdOo));
904 
905   free(dlEntryId);
906   free(dlEntryIdOo);
907 
908   dlMembers.mNbEntries++;
909   dlMembersOneOff.mNbEntries++;
910 
911   counts[0] = dlMembers.mNbEntries;
912   counts[1] = dlMembersOneOff.mNbEntries;
913   if (!SetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts)) {
914     PRINTF(("Cannot set DL members.\n"));
915     return FALSE;
916   }
917   return TRUE;
918 }
919 
SetPropertyUString(const nsMapiEntry & aObject,ULONG aPropertyTag,const char16_t * aValue)920 BOOL nsAbWinHelper::SetPropertyUString(const nsMapiEntry& aObject,
921                                        ULONG aPropertyTag,
922                                        const char16_t* aValue) {
923   SPropValue value;
924   nsAutoCString alternativeValue;
925 
926   value.ulPropTag = aPropertyTag;
927   if (PROP_TYPE(aPropertyTag) == PT_UNICODE) {
928     value.Value.lpszW =
929         reinterpret_cast<wchar_t*>(const_cast<char16_t*>(aValue));
930   } else if (PROP_TYPE(aPropertyTag) == PT_STRING8) {
931     alternativeValue = NS_LossyConvertUTF16toASCII(aValue);
932     value.Value.lpszA = const_cast<char*>(alternativeValue.get());
933   } else {
934     PRINTF(("Property %08lx is not a string.\n", aPropertyTag));
935     return FALSE;
936   }
937   return SetMAPIProperties(nullEntry, aObject, 1, &value, false);
938 }
939 
SetPropertiesUString(const nsMapiEntry & aDir,const nsMapiEntry & aObject,const ULONG aPropertyTags[],ULONG aNbProperties,nsString aValues[])940 BOOL nsAbWinHelper::SetPropertiesUString(const nsMapiEntry& aDir,
941                                          const nsMapiEntry& aObject,
942                                          const ULONG aPropertyTags[],
943                                          ULONG aNbProperties,
944                                          nsString aValues[]) {
945   LPSPropValue values = new SPropValue[aNbProperties];
946   if (!values) return FALSE;
947 
948   ULONG currentValue = 0;
949   nsAutoCString alternativeValue;
950   BOOL retCode = TRUE;
951 
952   for (ULONG i = 0; i < aNbProperties; ++i) {
953     values[currentValue].ulPropTag = aPropertyTags[i];
954     if (PROP_TYPE(aPropertyTags[i]) == PT_UNICODE) {
955       const wchar_t* value = aValues[i].get();
956       values[currentValue++].Value.lpszW = const_cast<wchar_t*>(value);
957     } else if (PROP_TYPE(aPropertyTags[i]) == PT_STRING8) {
958       LossyCopyUTF16toASCII(aValues[i], alternativeValue);
959       char* av = strdup(alternativeValue.get());
960       if (!av) {
961         retCode = FALSE;
962         break;
963       }
964       values[currentValue++].Value.lpszA = av;
965     }
966   }
967   if (retCode)
968     retCode = SetMAPIProperties(aDir, aObject, currentValue, values, true);
969   for (ULONG i = 0; i < currentValue; ++i) {
970     if (PROP_TYPE(aPropertyTags[i]) == PT_STRING8) {
971       free(values[i].Value.lpszA);
972     }
973   }
974   delete[] values;
975   return retCode;
976 }
977 
SetPropertyDate(const nsMapiEntry & aDir,const nsMapiEntry & aObject,bool fromContact,ULONG aPropertyTag,WORD aYear,WORD aMonth,WORD aDay)978 BOOL nsAbWinHelper::SetPropertyDate(const nsMapiEntry& aDir,
979                                     const nsMapiEntry& aObject,
980                                     bool fromContact, ULONG aPropertyTag,
981                                     WORD aYear, WORD aMonth, WORD aDay) {
982   SPropValue value;
983 
984   value.ulPropTag = aPropertyTag;
985   if (PROP_TYPE(aPropertyTag) == PT_SYSTIME) {
986     SYSTEMTIME readableTime;
987 
988     readableTime.wYear = aYear;
989     readableTime.wMonth = aMonth;
990     readableTime.wDay = aDay;
991     readableTime.wDayOfWeek = 0;
992     readableTime.wHour = 0;
993     readableTime.wMinute = 0;
994     readableTime.wSecond = 0;
995     readableTime.wMilliseconds = 0;
996     if (SystemTimeToFileTime(&readableTime, &value.Value.ft)) {
997       return SetMAPIProperties(aDir, aObject, 1, &value, fromContact);
998     }
999     return TRUE;
1000   }
1001   return FALSE;
1002 }
1003 
CreateEntryInternal(const nsMapiEntry & aParent,nsMapiEntry & aNewEntry,const char * aContactClass,const wchar_t * aName)1004 BOOL nsAbWinHelper::CreateEntryInternal(const nsMapiEntry& aParent,
1005                                         nsMapiEntry& aNewEntry,
1006                                         const char* aContactClass,
1007                                         const wchar_t* aName) {
1008   // We create an IPM.Contact or IPM.DistList message in the contacts folder.
1009   // To find that folder, we look for our `aParent` in the hierarchy table
1010   // and use the matching `PR_CONTAB_FOLDER_ENTRYID` for the folder.
1011   nsMapiInterfaceWrapper<LPABCONT> rootFolder;
1012   nsMapiInterfaceWrapper<LPMAPITABLE> folders;
1013   ULONG objType = 0;
1014   mLastError = mAddressBook->OpenEntry(0, NULL, NULL, 0, &objType, rootFolder);
1015   if (HR_FAILED(mLastError)) {
1016     PRINTF(("Cannot open root %08lx (creating new entry).\n", mLastError));
1017     return FALSE;
1018   }
1019   mLastError = rootFolder->GetHierarchyTable(CONVENIENT_DEPTH, folders);
1020   if (HR_FAILED(mLastError)) {
1021     PRINTF(("Cannot get hierarchy %08lx (creating new entry).\n", mLastError));
1022     return FALSE;
1023   }
1024 
1025   // Request `PR_ENTRYID` and `PR_CONTAB_FOLDER_ENTRYID`.
1026 #define PR_CONTAB_FOLDER_ENTRYID PROP_TAG(PT_BINARY, 0x6610)
1027   static const SizedSPropTagArray(2, properties) = {
1028       2, {PR_ENTRYID, PR_CONTAB_FOLDER_ENTRYID}};
1029   mLastError = folders->SetColumns((LPSPropTagArray)&properties, 0);
1030   if (HR_FAILED(mLastError)) {
1031     PRINTF(("Cannot set columns %08lx (creating new entry).\n", mLastError));
1032     return FALSE;
1033   }
1034 
1035   ULONG rowCount = 0;
1036   bool found = false;
1037   nsMapiEntry conTab;
1038   mLastError = folders->GetRowCount(0, &rowCount);
1039   if (HR_SUCCEEDED(mLastError)) {
1040     do {
1041       LPSRowSet rowSet = NULL;
1042 
1043       rowCount = 0;
1044       mLastError = folders->QueryRows(1, 0, &rowSet);
1045       if (HR_SUCCEEDED(mLastError)) {
1046         rowCount = rowSet->cRows;
1047         if (rowCount > 0) {
1048           ULONG result;
1049           // Get entry ID from row and compare.
1050           SPropValue& colValue = rowSet->aRow->lpProps[0];
1051 
1052           mLastError = mAddressSession->CompareEntryIDs(
1053               aParent.mByteCount, aParent.mEntryId, colValue.Value.bin.cb,
1054               reinterpret_cast<LPENTRYID>(colValue.Value.bin.lpb), 0, &result);
1055           if (HR_FAILED(mLastError)) {
1056             PRINTF(("CompareEntryIDs failed with %08lx (creating new entry).\n",
1057                     mLastError));
1058           }
1059           if (result) {
1060             SPropValue& conTabValue = rowSet->aRow->lpProps[1];
1061             conTab.Assign(
1062                 conTabValue.Value.bin.cb,
1063                 reinterpret_cast<LPENTRYID>(conTabValue.Value.bin.lpb));
1064             found = true;
1065             break;
1066           }
1067         }
1068         MyFreeProws(rowSet);
1069       } else {
1070         PRINTF(("Cannot query rows %08lx (creating new entry).\n", mLastError));
1071       }
1072     } while (rowCount > 0);
1073   }
1074   if (HR_FAILED(mLastError)) return HR_SUCCEEDED(mLastError);
1075 
1076   if (!found) {
1077     PRINTF(("Cannot find folder for contact in hierarchy table.\n"));
1078     return FALSE;
1079   }
1080 
1081   // Open store and contact folder.
1082   PRINTF(("Found contact folder associated with AB container.\n"));
1083   nsMapiEntry storeEntry;
1084   // Get the entry ID of the related store. This won't work for the
1085   // Global Address List (GAL) since it doesn't provide contacts from a
1086   // local store.
1087   if (!GetPropertyBin(aParent, PR_STORE_ENTRYID, storeEntry)) {
1088     PRINTF(("Cannot get PR_STORE_ENTRYID, likely not a local AB.\n"));
1089     return FALSE;
1090   }
1091   nsMapiInterfaceWrapper<LPMDB> store;
1092   mLastError = mAddressSession->OpenMsgStore(
1093       0, storeEntry.mByteCount, storeEntry.mEntryId, NULL, 0, store);
1094   if (HR_FAILED(mLastError)) {
1095     PRINTF(("Cannot open MAPI message store %08lx.\n", mLastError));
1096     return FALSE;
1097   }
1098   nsMapiInterfaceWrapper<LPMAPIFOLDER> contactFolder;
1099   mLastError =
1100       store->OpenEntry(conTab.mByteCount, conTab.mEntryId, &IID_IMAPIFolder,
1101                        MAPI_MODIFY, &objType, contactFolder);
1102   if (HR_FAILED(mLastError)) {
1103     PRINTF(("Cannot open contact folder %08lx.\n", mLastError));
1104     return FALSE;
1105   }
1106 
1107   // Crazy as it seems, contacts and distribution lists are stored as message.
1108   nsMapiInterfaceWrapper<LPMESSAGE> newEntry;
1109   mLastError = contactFolder->CreateMessage(&IID_IMessage, 0, newEntry);
1110   if (HR_FAILED(mLastError)) {
1111     PRINTF(("Cannot create new entry %08lx.\n", mLastError));
1112     return FALSE;
1113   }
1114 
1115   SPropValue propValue;
1116   LPSPropProblemArray problems = NULL;
1117   propValue.ulPropTag = PR_MESSAGE_CLASS_A;
1118   propValue.Value.lpszA = const_cast<char*>(aContactClass);
1119   mLastError = newEntry->SetProps(1, &propValue, &problems);
1120   if (HR_FAILED(mLastError)) {
1121     PRINTF(("Cannot set message class %08lx.\n", mLastError));
1122     return FALSE;
1123   }
1124 
1125   if (strcmp(aContactClass, "IPM.DistList") == 0) {
1126     // Set distribution list name.
1127     problems = NULL;
1128     GetDlNameTag(newEntry.Get(), propValue.ulPropTag);
1129     propValue.Value.lpszW = const_cast<wchar_t*>(aName);
1130     mLastError = newEntry->SetProps(1, &propValue, &problems);
1131     if (HR_FAILED(mLastError)) {
1132       PRINTF(("Cannot set DL name %08lx.\n", mLastError));
1133       return FALSE;
1134     }
1135   }
1136 
1137   mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY);
1138   if (HR_FAILED(mLastError)) {
1139     PRINTF(("Cannot commit new entry %08lx.\n", mLastError));
1140     return FALSE;
1141   }
1142 
1143   // Get the entry ID of the contact (IMessage).
1144   SPropTagArray property;
1145   LPSPropValue value = NULL;
1146   ULONG valueCount = 0;
1147   property.cValues = 1;
1148   property.aulPropTag[0] = PR_ENTRYID;
1149   mLastError = newEntry->GetProps(&property, 0, &valueCount, &value);
1150   if (HR_FAILED(mLastError) || valueCount != 1) {
1151     PRINTF(("Cannot get entry id %08lx.\n", mLastError));
1152     return FALSE;
1153   }
1154 
1155   // Construct the entry ID of the related address book entry (IMailUser).
1156   AbEntryId* abEntryId =
1157       (AbEntryId*)moz_xmalloc(sizeof(AbEntryId) + value->Value.bin.cb);
1158   if (!abEntryId) return FALSE;
1159   memset(abEntryId, 0, 4);  // Null out the flags.
1160   memcpy(abEntryId->provider, CONTAB_PROVIDER_ID, CONTAB_PROVIDER_ID_LENGTH);
1161   memcpy(abEntryId->version, ABENTRY_VERSION, ABENTRY_VERSION_LENGTH);
1162   memcpy(abEntryId->type, ABENTRY_TYPE, ABENTRY_TYPE_LENGTH);
1163   abEntryId->index = 0;
1164   abEntryId->length = value->Value.bin.cb;
1165   memcpy(abEntryId->idBytes, value->Value.bin.lpb, abEntryId->length);
1166 
1167   aNewEntry.Assign(sizeof(AbEntryId) + value->Value.bin.cb,
1168                    reinterpret_cast<LPENTRYID>(abEntryId));
1169   FreeBuffer(value);
1170 
1171   // We need to set a display name otherwise MAPI is really unhappy internally.
1172   SPropValue displayName;
1173   displayName.ulPropTag = PR_DISPLAY_NAME_W;
1174   displayName.Value.lpszW = const_cast<wchar_t*>(aName);
1175   nsMapiInterfaceWrapper<LPMAPIPROP> object;
1176   mLastError =
1177       mAddressBook->OpenEntry(aNewEntry.mByteCount, aNewEntry.mEntryId,
1178                               &IID_IMAPIProp, MAPI_MODIFY, &objType, object);
1179   if (HR_FAILED(mLastError)) {
1180     PRINTF(("Cannot open newly created AB entry %08lx.\n", mLastError));
1181     return FALSE;
1182   }
1183   mLastError = object->SetProps(1, &displayName, &problems);
1184   if (HR_FAILED(mLastError)) {
1185     PRINTF(("Cannot set display name %08lx.\n", mLastError));
1186     return FALSE;
1187   }
1188 
1189   return TRUE;
1190 }
1191 
CreateEntry(const nsMapiEntry & aParent,nsMapiEntry & aNewEntry)1192 BOOL nsAbWinHelper::CreateEntry(const nsMapiEntry& aParent,
1193                                 nsMapiEntry& aNewEntry) {
1194   nsAutoString tempName(L"" kDummyDisplayName);
1195   tempName.AppendInt(sEntryCounter++);
1196   return CreateEntryInternal(aParent, aNewEntry, "IPM.Contact", tempName.get());
1197 }
1198 
CreateDistList(const nsMapiEntry & aParent,nsMapiEntry & aNewEntry,const wchar_t * aName)1199 BOOL nsAbWinHelper::CreateDistList(const nsMapiEntry& aParent,
1200                                    nsMapiEntry& aNewEntry,
1201                                    const wchar_t* aName) {
1202   return CreateEntryInternal(aParent, aNewEntry, "IPM.DistList", aName);
1203 }
1204 
1205 enum {
1206   ContentsColumnEntryId = 0,
1207   ContentsColumnObjectType,
1208   ContentsColumnsSize
1209 };
1210 
1211 static const SizedSPropTagArray(ContentsColumnsSize, ContentsColumns) = {
1212     ContentsColumnsSize, {PR_ENTRYID, PR_OBJECT_TYPE}};
1213 
GetContents(const nsMapiEntry & aParent,LPSRestriction aRestriction,nsMapiEntry ** aList,ULONG & aNbElements,ULONG aMapiType)1214 BOOL nsAbWinHelper::GetContents(const nsMapiEntry& aParent,
1215                                 LPSRestriction aRestriction,
1216                                 nsMapiEntry** aList, ULONG& aNbElements,
1217                                 ULONG aMapiType) {
1218   if (aList != NULL) {
1219     *aList = NULL;
1220   }
1221   aNbElements = 0;
1222   nsMapiInterfaceWrapper<LPMAPICONTAINER> parent;
1223   nsMapiInterfaceWrapper<LPMAPITABLE> contents;
1224   ULONG objType = 0;
1225   ULONG rowCount = 0;
1226 
1227   mLastError =
1228       mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId,
1229                               &IID_IMAPIContainer, 0, &objType, parent);
1230   if (HR_FAILED(mLastError)) {
1231     PRINTF(("Cannot open parent %08lx.\n", mLastError));
1232     return FALSE;
1233   }
1234   // Historic comment: May be relevant in the future.
1235   // WAB removed in bug 1687132.
1236   // Here, flags for WAB and MAPI could be different, so this works
1237   // only as long as we don't want to use any flag in GetContentsTable
1238   mLastError = parent->GetContentsTable(0, contents);
1239   if (HR_FAILED(mLastError)) {
1240     PRINTF(("Cannot get contents %08lx.\n", mLastError));
1241     return FALSE;
1242   }
1243   if (aRestriction != NULL) {
1244     mLastError = contents->Restrict(aRestriction, 0);
1245     if (HR_FAILED(mLastError)) {
1246       PRINTF(("Cannot set restriction %08lx.\n", mLastError));
1247       return FALSE;
1248     }
1249   }
1250   mLastError = contents->SetColumns((LPSPropTagArray)&ContentsColumns, 0);
1251   if (HR_FAILED(mLastError)) {
1252     PRINTF(("Cannot set columns %08lx.\n", mLastError));
1253     return FALSE;
1254   }
1255   mLastError = contents->GetRowCount(0, &rowCount);
1256   if (HR_FAILED(mLastError)) {
1257     PRINTF(("Cannot get result count %08lx.\n", mLastError));
1258     return FALSE;
1259   }
1260   if (aList != NULL) {
1261     *aList = new nsMapiEntry[rowCount];
1262   }
1263   aNbElements = 0;
1264   do {
1265     LPSRowSet rowSet = NULL;
1266 
1267     rowCount = 0;
1268     mLastError = contents->QueryRows(1, 0, &rowSet);
1269     if (HR_FAILED(mLastError)) {
1270       PRINTF(("Cannot query rows %08lx.\n", mLastError));
1271       return FALSE;
1272     }
1273     rowCount = rowSet->cRows;
1274     if (rowCount > 0 &&
1275         (aMapiType == 0 ||
1276          rowSet->aRow->lpProps[ContentsColumnObjectType].Value.ul ==
1277              aMapiType)) {
1278       if (aList != NULL) {
1279         nsMapiEntry& current = (*aList)[aNbElements];
1280         SPropValue& currentValue = rowSet->aRow->lpProps[ContentsColumnEntryId];
1281 
1282         // Sometimes Outlooks spits the dummy here :-(
1283         // That is meant to be a byte count and NOT an error code of 0x8004010F.
1284         // We gloss over it.
1285         if (currentValue.Value.bin.cb == MAPI_E_NOT_FOUND ||
1286             currentValue.Value.bin.lpb == NULL) {
1287           PRINTF(("Error fetching rows.\n"));
1288           return TRUE;
1289         }
1290         current.Assign(currentValue.Value.bin.cb,
1291                        reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb));
1292       }
1293       ++aNbElements;
1294     }
1295     MyFreeProws(rowSet);
1296   } while (rowCount > 0);
1297   return TRUE;
1298 }
1299 
OpenMAPIObject(const nsMapiEntry & aDir,const nsMapiEntry & aObject,bool aFromContact,ULONG aFlags,LPUNKNOWN * aResult)1300 HRESULT nsAbWinHelper::OpenMAPIObject(const nsMapiEntry& aDir,
1301                                       const nsMapiEntry& aObject,
1302                                       bool aFromContact, ULONG aFlags,
1303                                       LPUNKNOWN* aResult) {
1304   nsMapiEntry storeEntry;
1305   ULONG contactIdLength = 0;
1306   LPENTRYID contactId = NULL;
1307   if (aFromContact) {
1308     // Get the entry ID of the related store. This won't work for the
1309     // Global Address List (GAL) since it doesn't provide contacts from a
1310     // local store.
1311     if (!GetPropertyBin(aDir, PR_STORE_ENTRYID, storeEntry)) {
1312       PRINTF(("Cannot get PR_STORE_ENTRYID, likely not a local AB.\n"));
1313       aFromContact = false;
1314     }
1315     // Check for magic provider GUID.
1316     struct AbEntryId* abEntryId = (struct AbEntryId*)aObject.mEntryId;
1317     if (memcmp(abEntryId->provider, CONTAB_PROVIDER_ID,
1318                CONTAB_PROVIDER_ID_LENGTH) != 0) {
1319       aFromContact = false;
1320     } else {
1321       contactIdLength = abEntryId->length;
1322       contactId = reinterpret_cast<LPENTRYID>(&(abEntryId->idBytes));
1323     }
1324   }
1325 
1326   ULONG objType = 0;
1327   if (aFromContact) {
1328     // Open the store.
1329     HRESULT retCode;
1330     nsMapiInterfaceWrapper<LPMDB> store;
1331     retCode = mAddressSession->OpenMsgStore(
1332         0, storeEntry.mByteCount, storeEntry.mEntryId, NULL, 0, store);
1333     if (HR_FAILED(retCode)) {
1334       PRINTF(("Cannot open MAPI message store %08lx.\n", retCode));
1335       return retCode;
1336     }
1337     // Open the contact object.
1338     retCode = store->OpenEntry(contactIdLength, contactId, &IID_IMessage, 0,
1339                                &objType, aResult);
1340     return retCode;
1341   } else {
1342     // Open the address book object.
1343     return mAddressBook->OpenEntry(aObject.mByteCount, aObject.mEntryId,
1344                                    &IID_IMAPIProp, 0, &objType, aResult);
1345   }
1346 }
1347 
GetMAPIProperties(const nsMapiEntry & aDir,const nsMapiEntry & aObject,const ULONG aPropertyTags[],ULONG aNbProperties,LPSPropValue & aValue,ULONG & aValueCount,bool aFromContact)1348 BOOL nsAbWinHelper::GetMAPIProperties(const nsMapiEntry& aDir,
1349                                       const nsMapiEntry& aObject,
1350                                       const ULONG aPropertyTags[],
1351                                       ULONG aNbProperties, LPSPropValue& aValue,
1352                                       ULONG& aValueCount, bool aFromContact) {
1353   nsMapiInterfaceWrapper<LPMAPIPROP> object;
1354   LPSPropTagArray properties = NULL;
1355 
1356   mLastError = OpenMAPIObject(aDir, aObject, aFromContact, 0, object);
1357   if (HR_FAILED(mLastError)) {
1358     PRINTF(("Cannot open entry %08lx.\n", mLastError));
1359     return FALSE;
1360   }
1361   AllocateBuffer(CbNewSPropTagArray(aNbProperties),
1362                  reinterpret_cast<void**>(&properties));
1363   properties->cValues = aNbProperties;
1364   for (ULONG i = 0; i < aNbProperties; ++i) {
1365     properties->aulPropTag[i] = aPropertyTags[i];
1366   }
1367   mLastError = object->GetProps(properties, 0, &aValueCount, &aValue);
1368   FreeBuffer(properties);
1369   if (HR_FAILED(mLastError)) {
1370     PRINTF(("Cannot get props %08lx.\n", mLastError));
1371   }
1372   return HR_SUCCEEDED(mLastError);
1373 }
1374 
SetMAPIProperties(const nsMapiEntry & aDir,const nsMapiEntry & aObject,ULONG aNbProperties,const LPSPropValue & aValues,bool aFromContact)1375 BOOL nsAbWinHelper::SetMAPIProperties(const nsMapiEntry& aDir,
1376                                       const nsMapiEntry& aObject,
1377                                       ULONG aNbProperties,
1378                                       const LPSPropValue& aValues,
1379                                       bool aFromContact) {
1380   nsMapiInterfaceWrapper<LPMAPIPROP> object;
1381   LPSPropProblemArray problems = NULL;
1382 
1383   mLastError = OpenMAPIObject(aDir, aObject, aFromContact, MAPI_MODIFY, object);
1384   if (HR_FAILED(mLastError)) {
1385     PRINTF(("Cannot open entry %08lx.\n", mLastError));
1386     return FALSE;
1387   }
1388   mLastError = object->SetProps(aNbProperties, aValues, &problems);
1389   if (HR_FAILED(mLastError)) {
1390     PRINTF(("Cannot update the object %08lx.\n", mLastError));
1391     return FALSE;
1392   }
1393   if (problems != NULL) {
1394     for (ULONG i = 0; i < problems->cProblem; ++i) {
1395       PRINTF(("Problem %lu: index %lu code %08lx.\n", i,
1396               problems->aProblem[i].ulIndex, problems->aProblem[i].scode));
1397     }
1398     mAddressFreeBuffer(problems);
1399   }
1400   mLastError = object->SaveChanges(0);
1401   if (HR_FAILED(mLastError)) {
1402     PRINTF(("Cannot commit changes %08lx.\n", mLastError));
1403   }
1404   return HR_SUCCEEDED(mLastError);
1405 }
1406 
DeleteMAPIProperties(const nsMapiEntry & aDir,const nsMapiEntry & aObject,const LPSPropTagArray aProps,bool aFromContact)1407 BOOL nsAbWinHelper::DeleteMAPIProperties(const nsMapiEntry& aDir,
1408                                          const nsMapiEntry& aObject,
1409                                          const LPSPropTagArray aProps,
1410                                          bool aFromContact) {
1411   nsMapiInterfaceWrapper<LPMAPIPROP> object;
1412   LPSPropProblemArray problems = NULL;
1413 
1414   mLastError = OpenMAPIObject(aDir, aObject, aFromContact, MAPI_MODIFY, object);
1415   if (HR_FAILED(mLastError)) {
1416     PRINTF(("Cannot open entry %08lx.\n", mLastError));
1417     return FALSE;
1418   }
1419   mLastError = object->DeleteProps(aProps, &problems);
1420   if (HR_FAILED(mLastError)) {
1421     PRINTF(("Cannot update the object (DeleteProps) %08lx.\n", mLastError));
1422     return FALSE;
1423   }
1424   if (problems != NULL) {
1425     for (ULONG i = 0; i < problems->cProblem; ++i) {
1426       PRINTF(("Problem %lu: index %lu code %08lx.\n", i,
1427               problems->aProblem[i].ulIndex, problems->aProblem[i].scode));
1428     }
1429     mAddressFreeBuffer(problems);
1430   }
1431   mLastError = object->SaveChanges(0);
1432   if (HR_FAILED(mLastError)) {
1433     PRINTF(("Cannot commit changes %08lx.\n", mLastError));
1434   }
1435   return HR_SUCCEEDED(mLastError);
1436 }
1437 
MyFreeProws(LPSRowSet aRowset)1438 void nsAbWinHelper::MyFreeProws(LPSRowSet aRowset) {
1439   if (aRowset == NULL) {
1440     return;
1441   }
1442   ULONG i = 0;
1443 
1444   for (i = 0; i < aRowset->cRows; ++i) {
1445     FreeBuffer(aRowset->aRow[i].lpProps);
1446   }
1447   FreeBuffer(aRowset);
1448 }
1449 
nsAbWinHelperGuard()1450 nsAbWinHelperGuard::nsAbWinHelperGuard() : mHelper(NULL) {
1451   mHelper = new nsMapiAddressBook;
1452 }
1453 
~nsAbWinHelperGuard(void)1454 nsAbWinHelperGuard::~nsAbWinHelperGuard(void) { delete mHelper; }
1455 
makeEntryIdFromURI(const char * aScheme,const char * aUri,nsCString & aEntry)1456 void makeEntryIdFromURI(const char* aScheme, const char* aUri,
1457                         nsCString& aEntry) {
1458   aEntry.Truncate();
1459   uint32_t schemeLength = strlen(aScheme);
1460 
1461   if (strncmp(aUri, aScheme, schemeLength) == 0) {
1462     // Assign string from position `schemeLength`.
1463     aEntry = aUri + schemeLength;
1464 
1465     // Now strip the parent directory before the /.
1466     int ind = aEntry.FindChar('/');
1467     if (ind != kNotFound) {
1468       aEntry = Substring(aEntry, ind + 1);
1469     }
1470   }
1471 }
1472 
CompareEntryIDs(nsCString & aEntryID1,nsCString & aEntryID2)1473 bool nsAbWinHelper::CompareEntryIDs(nsCString& aEntryID1,
1474                                     nsCString& aEntryID2) {
1475   ULONG result;
1476   nsMapiEntry e1;
1477   nsMapiEntry e2;
1478   e1.Assign(aEntryID1);
1479   e2.Assign(aEntryID2);
1480   mLastError = mAddressSession->CompareEntryIDs(
1481       e1.mByteCount, e1.mEntryId, e2.mByteCount, e2.mEntryId, 0, &result);
1482   if (HR_FAILED(mLastError)) {
1483     PRINTF(("CompareEntryIDs failed with %08lx (CompareEntryIDs()).\n",
1484             mLastError));
1485     return false;
1486   }
1487   return result ? true : false;
1488 }
1489