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