1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include <mapidefs.h>
6 #include <mapi.h>
7 #include <winstring.h>
8 #include "msgMapiImp.h"
9 #include "msgMapiFactory.h"
10 #include "msgMapiMain.h"
11 
12 #include "nsIMsgCompFields.h"
13 #include "msgMapiHook.h"
14 #include "nsString.h"
15 #include "nsCOMPtr.h"
16 #include "nsISupports.h"
17 #include "nsMsgCompCID.h"
18 #include "nsIMsgDatabase.h"
19 #include "nsMsgFolderFlags.h"
20 #include "nsIMsgHdr.h"
21 #include "MailNewsTypes.h"
22 #include "nsMsgBaseCID.h"
23 #include "nsIMsgAccountManager.h"
24 #include "nsIMsgFolder.h"
25 #include "nsIMsgImapMailFolder.h"
26 #include <time.h>
27 #include "nsIInputStream.h"
28 #include "nsILineInputStream.h"
29 #include "nsISeekableStream.h"
30 #include "nsIFile.h"
31 #include "nsIFileStreams.h"
32 #include "nsNetCID.h"
33 #include "nsMsgMessageFlags.h"
34 #include "mozilla/mailnews/MimeHeaderParser.h"
35 #include "mozilla/Logging.h"
36 
37 using namespace mozilla::mailnews;
38 
39 mozilla::LazyLogModule MAPI("MAPI");
40 
CMapiImp()41 CMapiImp::CMapiImp() : m_cRef(1) { m_Lock = PR_NewLock(); }
42 
~CMapiImp()43 CMapiImp::~CMapiImp() {
44   if (m_Lock) PR_DestroyLock(m_Lock);
45 }
46 
QueryInterface(const IID & aIid,void ** aPpv)47 STDMETHODIMP CMapiImp::QueryInterface(const IID& aIid, void** aPpv) {
48   if (aIid == IID_IUnknown) {
49     *aPpv = static_cast<nsIMapi*>(this);
50   } else if (aIid == IID_nsIMapi) {
51     *aPpv = static_cast<nsIMapi*>(this);
52   } else {
53     *aPpv = nullptr;
54     return E_NOINTERFACE;
55   }
56 
57   reinterpret_cast<IUnknown*>(*aPpv)->AddRef();
58   return S_OK;
59 }
60 
STDMETHODIMP_(ULONG)61 STDMETHODIMP_(ULONG) CMapiImp::AddRef() { return ++m_cRef; }
62 
STDMETHODIMP_(ULONG)63 STDMETHODIMP_(ULONG) CMapiImp::Release() {
64   int32_t temp = --m_cRef;
65   if (m_cRef == 0) {
66     delete this;
67     return 0;
68   }
69 
70   return temp;
71 }
72 
IsValid()73 STDMETHODIMP CMapiImp::IsValid() { return S_OK; }
74 
IsValidSession(unsigned long aSession)75 STDMETHODIMP CMapiImp::IsValidSession(unsigned long aSession) {
76   nsMAPIConfiguration* pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
77   if (pConfig && pConfig->IsSessionValid(aSession)) return S_OK;
78 
79   return E_FAIL;
80 }
81 
Initialize()82 STDMETHODIMP CMapiImp::Initialize() {
83   HRESULT hr = E_FAIL;
84 
85   if (!m_Lock) return E_FAIL;
86 
87   PR_Lock(m_Lock);
88 
89   // Initialize MAPI Configuration
90 
91   nsMAPIConfiguration* pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
92   if (pConfig != nullptr) hr = S_OK;
93 
94   PR_Unlock(m_Lock);
95 
96   return hr;
97 }
98 
Login(unsigned long aUIArg,LPSTR aLogin,LPSTR aPassWord,unsigned long aFlags,unsigned long * aSessionId)99 STDMETHODIMP CMapiImp::Login(unsigned long aUIArg, LPSTR aLogin,
100                              LPSTR aPassWord, unsigned long aFlags,
101                              unsigned long* aSessionId) {
102   HRESULT hr = E_FAIL;
103   bool bNewSession = false;
104   nsCString id_key;
105 
106   MOZ_LOG(MAPI, mozilla::LogLevel::Debug,
107           ("CMapiImp::Login using flags %d", aFlags));
108   if (aFlags & MAPI_NEW_SESSION) bNewSession = true;
109 
110   // Check For Profile Name
111   if (aLogin != nullptr && aLogin[0] != '\0') {
112     if (!nsMapiHook::VerifyUserName(nsDependentCString(aLogin), id_key)) {
113       *aSessionId = MAPI_E_LOGIN_FAILURE;
114       MOZ_LOG(MAPI, mozilla::LogLevel::Debug,
115               ("CMapiImp::Login failed for username %s", aLogin));
116       NS_ASSERTION(false, "failed verifying user name");
117       return hr;
118     }
119   } else {
120     // get default account
121     nsresult rv;
122     nsCOMPtr<nsIMsgAccountManager> accountManager =
123         do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
124     NS_ENSURE_SUCCESS(rv, MAPI_E_LOGIN_FAILURE);
125 
126     nsCOMPtr<nsIMsgAccount> account;
127     rv = accountManager->GetDefaultAccount(getter_AddRefs(account));
128     NS_ENSURE_SUCCESS(rv, MAPI_E_LOGIN_FAILURE);
129     if (!account) return MAPI_E_LOGIN_FAILURE;
130 
131     nsCOMPtr<nsIMsgIdentity> identity;
132     rv = account->GetDefaultIdentity(getter_AddRefs(identity));
133     NS_ENSURE_SUCCESS(rv, MAPI_E_LOGIN_FAILURE);
134     if (!identity) return MAPI_E_LOGIN_FAILURE;
135     identity->GetKey(id_key);
136   }
137 
138   // finally register(create) the session.
139   uint32_t nSession_Id;
140   int16_t nResult = 0;
141 
142   nsMAPIConfiguration* pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
143   if (pConfig != nullptr)
144     nResult = pConfig->RegisterSession(
145         aUIArg, aLogin ? nsDependentCString(aLogin) : EmptyCString(),
146         aPassWord ? nsDependentCString(aPassWord) : EmptyCString(),
147         (aFlags & MAPI_FORCE_DOWNLOAD), bNewSession, &nSession_Id,
148         id_key.get());
149   switch (nResult) {
150     case -1: {
151       *aSessionId = MAPI_E_TOO_MANY_SESSIONS;
152       return hr;
153     }
154     case 0: {
155       *aSessionId = MAPI_E_INSUFFICIENT_MEMORY;
156       return hr;
157     }
158     default: {
159       *aSessionId = nSession_Id;
160       MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::Login succeeded"));
161       break;
162     }
163   }
164 
165   return S_OK;
166 }
167 
SendMail(unsigned long aSession,lpnsMapiMessage aMessage,unsigned long aFlags,unsigned long aReserved)168 STDMETHODIMP CMapiImp::SendMail(unsigned long aSession,
169                                 lpnsMapiMessage aMessage, unsigned long aFlags,
170                                 unsigned long aReserved) {
171   MOZ_LOG(MAPI, mozilla::LogLevel::Debug,
172           ("CMapiImp::SendMail flags=%lx subject: %s sender: %s", aFlags,
173            (aMessage && aMessage->lpszSubject) ? aMessage->lpszSubject
174                                                : "(no subject)",
175            (aMessage && aMessage->lpOriginator &&
176             aMessage->lpOriginator->lpszAddress)
177                ? aMessage->lpOriginator->lpszAddress
178                : "(no sender)"));
179 
180   /** create nsIMsgCompFields obj and populate it **/
181   nsresult rv = NS_OK;
182   nsCOMPtr<nsIMsgCompFields> pCompFields =
183       do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv);
184   if (NS_FAILED(rv) || (!pCompFields)) return MAPI_E_INSUFFICIENT_MEMORY;
185 
186   if (aMessage)
187     rv = nsMapiHook::PopulateCompFieldsWithConversion(aMessage, pCompFields);
188 
189   if (NS_SUCCEEDED(rv)) {
190     // see flag to see if UI needs to be brought up
191     if (!(aFlags & MAPI_DIALOG)) {
192       rv = nsMapiHook::BlindSendMail(aSession, pCompFields);
193     } else {
194       rv = nsMapiHook::ShowComposerWindow(aSession, pCompFields);
195     }
196   }
197 
198   return nsMAPIConfiguration::GetMAPIErrorFromNSError(rv);
199 }
200 
SendMailW(unsigned long aSession,lpnsMapiMessageW aMessage,unsigned long aFlags,unsigned long aReserved)201 STDMETHODIMP CMapiImp::SendMailW(unsigned long aSession,
202                                  lpnsMapiMessageW aMessage,
203                                  unsigned long aFlags,
204                                  unsigned long aReserved) {
205   MOZ_LOG(
206       MAPI, mozilla::LogLevel::Debug,
207       ("CMapiImp::SendMailW flags=%lx subject: %s sender: %s", aFlags,
208        (aMessage && aMessage->lpszSubject)
209            ? NS_ConvertUTF16toUTF8(aMessage->lpszSubject).get()
210            : "(no subject)",
211        (aMessage && aMessage->lpOriginator &&
212         aMessage->lpOriginator->lpszAddress)
213            ? NS_ConvertUTF16toUTF8(aMessage->lpOriginator->lpszAddress).get()
214            : "(no sender)"));
215 
216   // Create nsIMsgCompFields obj and populate it.
217   nsresult rv = NS_OK;
218   nsCOMPtr<nsIMsgCompFields> pCompFields =
219       do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv);
220   if (NS_FAILED(rv) || !pCompFields) return MAPI_E_INSUFFICIENT_MEMORY;
221 
222   if (aMessage) rv = nsMapiHook::PopulateCompFieldsW(aMessage, pCompFields);
223 
224   if (NS_SUCCEEDED(rv)) {
225     // Check flag to see if UI needs to be brought up.
226     if (!(aFlags & MAPI_DIALOG)) {
227       rv = nsMapiHook::BlindSendMail(aSession, pCompFields);
228     } else {
229       rv = nsMapiHook::ShowComposerWindow(aSession, pCompFields);
230     }
231   }
232 
233   return nsMAPIConfiguration::GetMAPIErrorFromNSError(rv);
234 }
235 
SendDocuments(unsigned long aSession,LPSTR aDelimChar,LPSTR aFilePaths,LPSTR aFileNames,ULONG aFlags)236 STDMETHODIMP CMapiImp::SendDocuments(unsigned long aSession, LPSTR aDelimChar,
237                                      LPSTR aFilePaths, LPSTR aFileNames,
238                                      ULONG aFlags) {
239   nsresult rv = NS_OK;
240 
241   MOZ_LOG(MAPI, mozilla::LogLevel::Debug,
242           ("CMapiImp::SendDocument using flags %d", aFlags));
243   /** create nsIMsgCompFields obj and populate it **/
244   nsCOMPtr<nsIMsgCompFields> pCompFields =
245       do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv);
246   if (NS_FAILED(rv) || (!pCompFields)) return MAPI_E_INSUFFICIENT_MEMORY;
247 
248   if (aFilePaths) {
249     rv = nsMapiHook::PopulateCompFieldsForSendDocs(pCompFields, aFlags,
250                                                    aDelimChar, aFilePaths);
251   }
252 
253   if (NS_SUCCEEDED(rv))
254     rv = nsMapiHook::ShowComposerWindow(aSession, pCompFields);
255   else
256     MOZ_LOG(MAPI, mozilla::LogLevel::Debug,
257             ("CMapiImp::SendDocument error rv = %lx, paths = %s names = %s", rv,
258              aFilePaths, aFileNames));
259 
260   return nsMAPIConfiguration::GetMAPIErrorFromNSError(rv);
261 }
262 
GetDefaultInbox(nsIMsgFolder ** inboxFolder)263 nsresult CMapiImp::GetDefaultInbox(nsIMsgFolder** inboxFolder) {
264   // get default account
265   nsresult rv;
266   nsCOMPtr<nsIMsgAccountManager> accountManager =
267       do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
268   NS_ENSURE_SUCCESS(rv, rv);
269 
270   nsCOMPtr<nsIMsgAccount> account;
271   rv = accountManager->GetDefaultAccount(getter_AddRefs(account));
272   NS_ENSURE_SUCCESS(rv, rv);
273   if (!account) return NS_ERROR_FAILURE;
274 
275   // get incoming server
276   nsCOMPtr<nsIMsgIncomingServer> server;
277   rv = account->GetIncomingServer(getter_AddRefs(server));
278   NS_ENSURE_SUCCESS(rv, rv);
279 
280   nsCString type;
281   rv = server->GetType(type);
282   NS_ENSURE_SUCCESS(rv, rv);
283 
284   // we only care about imap and pop3
285   if (type.EqualsLiteral("imap") || type.EqualsLiteral("pop3")) {
286     // imap and pop3 account should have an Inbox
287     nsCOMPtr<nsIMsgFolder> rootMsgFolder;
288     rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
289     NS_ENSURE_SUCCESS(rv, rv);
290 
291     if (!rootMsgFolder) return NS_ERROR_FAILURE;
292 
293     rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, inboxFolder);
294     if (!*inboxFolder) return NS_ERROR_FAILURE;
295   }
296   return NS_OK;
297 }
298 
299 //*****************************************************************************
300 // Encapsulate the XP DB stuff required to enumerate messages
301 
302 class MsgMapiListContext {
303  public:
MsgMapiListContext()304   MsgMapiListContext() {}
305   ~MsgMapiListContext();
306 
307   nsresult OpenDatabase(nsIMsgFolder* folder);
308 
309   nsMsgKey GetNext();
310   nsresult MarkRead(nsMsgKey key, bool read);
311 
312   lpnsMapiMessage GetMessage(nsMsgKey, unsigned long flFlags);
313   bool IsIMAPHost(void);
314   bool DeleteMessage(nsMsgKey key);
315 
316  protected:
317   char* ConvertDateToMapiFormat(time_t);
318   char* ConvertBodyToMapiFormat(nsIMsgDBHdr* hdr);
319   void ConvertRecipientsToMapiFormat(
320       const nsCOMArray<msgIAddressObject>& ourRecips,
321       lpnsMapiRecipDesc mapiRecips, int mapiRecipClass);
322 
323   nsCOMPtr<nsIMsgFolder> m_folder;
324   nsCOMPtr<nsIMsgDatabase> m_db;
325   nsCOMPtr<nsIMsgEnumerator> m_msgEnumerator;
326 };
327 
InitContext(unsigned long session,MsgMapiListContext ** listContext)328 LONG CMapiImp::InitContext(unsigned long session,
329                            MsgMapiListContext** listContext) {
330   nsMAPIConfiguration* pMapiConfig =
331       nsMAPIConfiguration::GetMAPIConfiguration();
332   if (!pMapiConfig) return MAPI_E_FAILURE;  // get the singleton obj
333   *listContext = (MsgMapiListContext*)pMapiConfig->GetMapiListContext(session);
334   // This is the first message
335   if (!*listContext) {
336     nsCOMPtr<nsIMsgFolder> inboxFolder;
337     nsresult rv = GetDefaultInbox(getter_AddRefs(inboxFolder));
338     if (NS_FAILED(rv)) {
339       NS_ASSERTION(false, "in init context, no inbox");
340       return (MAPI_E_NO_MESSAGES);
341     }
342 
343     *listContext = new MsgMapiListContext;
344     if (!*listContext) return MAPI_E_INSUFFICIENT_MEMORY;
345 
346     rv = (*listContext)->OpenDatabase(inboxFolder);
347     if (NS_FAILED(rv)) {
348       pMapiConfig->SetMapiListContext(session, NULL);
349       delete *listContext;
350       NS_ASSERTION(false, "in init context, unable to open db");
351       return MAPI_E_NO_MESSAGES;
352     } else
353       pMapiConfig->SetMapiListContext(session, *listContext);
354   }
355   return SUCCESS_SUCCESS;
356 }
357 
FindNext(unsigned long aSession,unsigned long ulUIParam,LPSTR lpszMessageType,LPSTR lpszSeedMessageID,unsigned long flFlags,unsigned long ulReserved,unsigned char lpszMessageID[64])358 STDMETHODIMP CMapiImp::FindNext(unsigned long aSession, unsigned long ulUIParam,
359                                 LPSTR lpszMessageType, LPSTR lpszSeedMessageID,
360                                 unsigned long flFlags, unsigned long ulReserved,
361                                 unsigned char lpszMessageID[64])
362 
363 {
364   //
365   // If this is true, then this is the first call to this FindNext function
366   // and we should start the enumeration operation.
367   //
368 
369   *lpszMessageID = '\0';
370   nsMAPIConfiguration* pMapiConfig =
371       nsMAPIConfiguration::GetMAPIConfiguration();
372   if (!pMapiConfig) {
373     NS_ASSERTION(false, "failed to get config in findnext");
374     return MAPI_E_FAILURE;  // get the singleton obj
375   }
376   MsgMapiListContext* listContext;
377   LONG ret = InitContext(aSession, &listContext);
378   if (ret != SUCCESS_SUCCESS) {
379     NS_ASSERTION(false, "init context failed");
380     return ret;
381   }
382   NS_ASSERTION(listContext, "initContext returned null context");
383   if (listContext) {
384     //    NS_ASSERTION(false, "find next init context succeeded");
385     nsMsgKey nextKey = listContext->GetNext();
386     if (nextKey == nsMsgKey_None) {
387       pMapiConfig->SetMapiListContext(aSession, NULL);
388       delete listContext;
389       return (MAPI_E_NO_MESSAGES);
390     }
391 
392     //    TRACE("MAPI: ProcessMAPIFindNext() Found message id = %d\n", nextKey);
393 
394     sprintf((char*)lpszMessageID, "%d", nextKey);
395   }
396 
397   MOZ_LOG(MAPI, mozilla::LogLevel::Debug,
398           ("CMapiImp::FindNext returning key %s", (char*)lpszMessageID));
399   return (SUCCESS_SUCCESS);
400 }
401 
ReadMail(unsigned long aSession,unsigned long ulUIParam,LPSTR lpszMessageID,unsigned long flFlags,unsigned long ulReserved,lpnsMapiMessage * lppMessage)402 STDMETHODIMP CMapiImp::ReadMail(unsigned long aSession, unsigned long ulUIParam,
403                                 LPSTR lpszMessageID, unsigned long flFlags,
404                                 unsigned long ulReserved,
405                                 lpnsMapiMessage* lppMessage) {
406   nsresult irv;
407   nsAutoCString keyString((char*)lpszMessageID);
408   MOZ_LOG(MAPI, mozilla::LogLevel::Debug,
409           ("CMapiImp::ReadMail asking for key %s", (char*)lpszMessageID));
410   nsMsgKey msgKey = keyString.ToInteger(&irv);
411   if (NS_FAILED(irv)) {
412     NS_ASSERTION(false, "invalid lpszMessageID");
413     return MAPI_E_INVALID_MESSAGE;
414   }
415   MsgMapiListContext* listContext;
416   LONG ret = InitContext(aSession, &listContext);
417   if (ret != SUCCESS_SUCCESS) {
418     NS_ASSERTION(false, "init context failed in ReadMail");
419     return ret;
420   }
421   *lppMessage = listContext->GetMessage(msgKey, flFlags);
422   NS_ASSERTION(*lppMessage, "get message failed");
423 
424   return (*lppMessage) ? SUCCESS_SUCCESS : E_FAIL;
425 }
426 
DeleteMail(unsigned long aSession,unsigned long ulUIParam,LPSTR lpszMessageID,unsigned long flFlags,unsigned long ulReserved)427 STDMETHODIMP CMapiImp::DeleteMail(unsigned long aSession,
428                                   unsigned long ulUIParam, LPSTR lpszMessageID,
429                                   unsigned long flFlags,
430                                   unsigned long ulReserved) {
431   nsresult irv;
432   nsAutoCString keyString((char*)lpszMessageID);
433   nsMsgKey msgKey = keyString.ToInteger(&irv);
434   // XXX Why do we return success on failure?
435   if (NS_FAILED(irv)) return SUCCESS_SUCCESS;
436   MsgMapiListContext* listContext;
437   LONG ret = InitContext(aSession, &listContext);
438   if (ret != SUCCESS_SUCCESS) return ret;
439   return (listContext->DeleteMessage(msgKey)) ? SUCCESS_SUCCESS
440                                               : MAPI_E_INVALID_MESSAGE;
441 }
442 
SaveMail(unsigned long aSession,unsigned long ulUIParam,lpnsMapiMessage lppMessage,unsigned long flFlags,unsigned long ulReserved,LPSTR lpszMessageID)443 STDMETHODIMP CMapiImp::SaveMail(unsigned long aSession, unsigned long ulUIParam,
444                                 lpnsMapiMessage lppMessage,
445                                 unsigned long flFlags, unsigned long ulReserved,
446                                 LPSTR lpszMessageID) {
447   MsgMapiListContext* listContext;
448   LONG ret = InitContext(aSession, &listContext);
449   if (ret != SUCCESS_SUCCESS) return ret;
450   return S_OK;
451 }
452 
Logoff(unsigned long aSession)453 STDMETHODIMP CMapiImp::Logoff(unsigned long aSession) {
454   nsMAPIConfiguration* pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
455 
456   if (pConfig->UnRegisterSession((uint32_t)aSession)) return S_OK;
457 
458   return E_FAIL;
459 }
460 
CleanUp()461 STDMETHODIMP CMapiImp::CleanUp() {
462   nsMapiHook::CleanUp();
463   return S_OK;
464 }
465 
466 #define MAX_NAME_LEN 256
467 
~MsgMapiListContext()468 MsgMapiListContext::~MsgMapiListContext() {
469   if (m_db) m_db->Close(false);
470 }
471 
OpenDatabase(nsIMsgFolder * folder)472 nsresult MsgMapiListContext::OpenDatabase(nsIMsgFolder* folder) {
473   nsresult dbErr = NS_ERROR_FAILURE;
474   if (folder) {
475     m_folder = folder;
476     dbErr = folder->GetMsgDatabase(getter_AddRefs(m_db));
477     if (m_db) dbErr = m_db->EnumerateMessages(getter_AddRefs(m_msgEnumerator));
478   }
479   return dbErr;
480 }
481 
IsIMAPHost(void)482 bool MsgMapiListContext::IsIMAPHost(void) {
483   if (!m_folder) return FALSE;
484   nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder);
485 
486   return imapFolder != nullptr;
487 }
488 
GetNext()489 nsMsgKey MsgMapiListContext::GetNext() {
490   nsMsgKey key = nsMsgKey_None;
491   bool keepTrying = TRUE;
492 
493   //  NS_ASSERTION (m_msgEnumerator && m_db, "need enumerator and db");
494   if (m_msgEnumerator && m_db) {
495     do {
496       keepTrying = FALSE;
497       nsCOMPtr<nsIMsgDBHdr> msgHdr;
498       if (NS_SUCCEEDED(m_msgEnumerator->GetNext(getter_AddRefs(msgHdr))) &&
499           msgHdr) {
500         msgHdr->GetMessageKey(&key);
501 
502         // Check here for IMAP message...if not, just return...
503         if (!IsIMAPHost()) return key;
504 
505         // If this is an IMAP message, we have to make sure we have a valid
506         // body to work with.
507         uint32_t flags = 0;
508 
509         (void)msgHdr->GetFlags(&flags);
510         if (flags & nsMsgMessageFlags::Offline) return key;
511 
512         // Ok, if we get here, we have an IMAP message without a body!
513         // We need to keep trying by calling the GetNext member recursively...
514         keepTrying = TRUE;
515       }
516     } while (keepTrying);
517   }
518 
519   return key;
520 }
521 
MarkRead(nsMsgKey key,bool read)522 nsresult MsgMapiListContext::MarkRead(nsMsgKey key, bool read) {
523   nsresult err = NS_ERROR_FAILURE;
524   NS_ASSERTION(m_db, "no db");
525   if (m_db) err = m_db->MarkRead(key, read, nullptr);
526   return err;
527 }
528 
GetMessage(nsMsgKey key,unsigned long flFlags)529 lpnsMapiMessage MsgMapiListContext::GetMessage(nsMsgKey key,
530                                                unsigned long flFlags) {
531   lpnsMapiMessage message =
532       (lpnsMapiMessage)CoTaskMemAlloc(sizeof(nsMapiMessage));
533   memset(message, 0, sizeof(nsMapiMessage));
534   if (message) {
535     nsCString subject;
536     nsCString author;
537     nsCOMPtr<nsIMsgDBHdr> msgHdr;
538 
539     m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
540     if (msgHdr) {
541       msgHdr->GetSubject(getter_Copies(subject));
542       message->lpszSubject = (char*)CoTaskMemAlloc(subject.Length() + 1);
543       strcpy((char*)message->lpszSubject, subject.get());
544       uint32_t date;
545       (void)msgHdr->GetDateInSeconds(&date);
546       message->lpszDateReceived = ConvertDateToMapiFormat(date);
547 
548       // Pull out the flags info
549       // anything to do with MAPI_SENT? Since we're only reading the Inbox, I
550       // guess not
551       uint32_t ourFlags;
552       (void)msgHdr->GetFlags(&ourFlags);
553       if (!(ourFlags & nsMsgMessageFlags::Read))
554         message->flFlags |= MAPI_UNREAD;
555       if (ourFlags & (nsMsgMessageFlags::MDNReportNeeded |
556                       nsMsgMessageFlags::MDNReportSent))
557         message->flFlags |= MAPI_RECEIPT_REQUESTED;
558 
559       // Pull out the author/originator info
560       message->lpOriginator =
561           (lpnsMapiRecipDesc)CoTaskMemAlloc(sizeof(nsMapiRecipDesc));
562       memset(message->lpOriginator, 0, sizeof(nsMapiRecipDesc));
563       if (message->lpOriginator) {
564         msgHdr->GetAuthor(getter_Copies(author));
565         ConvertRecipientsToMapiFormat(EncodedHeader(author),
566                                       message->lpOriginator, MAPI_ORIG);
567       }
568       // Pull out the To/CC info
569       nsCString recipients, ccList;
570       msgHdr->GetRecipients(getter_Copies(recipients));
571       msgHdr->GetCcList(getter_Copies(ccList));
572 
573       nsCOMArray<msgIAddressObject> parsedToRecips = EncodedHeader(recipients);
574       nsCOMArray<msgIAddressObject> parsedCCRecips = EncodedHeader(ccList);
575       uint32_t numToRecips = parsedToRecips.Length();
576       uint32_t numCCRecips = parsedCCRecips.Length();
577 
578       message->lpRecips = (lpnsMapiRecipDesc)CoTaskMemAlloc(
579           (numToRecips + numCCRecips) * sizeof(MapiRecipDesc));
580       memset(message->lpRecips, 0,
581              (numToRecips + numCCRecips) * sizeof(MapiRecipDesc));
582       if (message->lpRecips) {
583         ConvertRecipientsToMapiFormat(parsedToRecips, message->lpRecips,
584                                       MAPI_TO);
585         ConvertRecipientsToMapiFormat(parsedCCRecips,
586                                       &message->lpRecips[numToRecips], MAPI_CC);
587       }
588 
589       MOZ_LOG(MAPI, mozilla::LogLevel::Debug,
590               ("MsgMapiListContext::GetMessage flags=%x subject %s date %s "
591                "sender %s",
592                flFlags, (char*)message->lpszSubject,
593                (char*)message->lpszDateReceived, author.get()));
594 
595       // Convert any body text that we have locally
596       if (!(flFlags & MAPI_ENVELOPE_ONLY))
597         message->lpszNoteText = (char*)ConvertBodyToMapiFormat(msgHdr);
598     }
599     if (!(flFlags & (MAPI_PEEK | MAPI_ENVELOPE_ONLY)))
600       m_db->MarkRead(key, true, nullptr);
601   }
602   return message;
603 }
604 
ConvertDateToMapiFormat(time_t ourTime)605 char* MsgMapiListContext::ConvertDateToMapiFormat(time_t ourTime) {
606   char* date = (char*)CoTaskMemAlloc(32);
607   if (date) {
608     // MAPI time format is YYYY/MM/DD HH:MM
609     // Note that we're not using XP_StrfTime because that localizes the time
610     // format, and the way I read the MAPI spec is that their format is
611     // canonical, not localized.
612     struct tm* local = localtime(&ourTime);
613     if (local)
614       strftime(date, 32, "%Y/%m/%d %I:%M",
615                local);  // use %H if hours should be 24 hour format
616   }
617   return date;
618 }
619 
ConvertRecipientsToMapiFormat(const nsCOMArray<msgIAddressObject> & recipients,lpnsMapiRecipDesc mapiRecips,int mapiRecipClass)620 void MsgMapiListContext::ConvertRecipientsToMapiFormat(
621     const nsCOMArray<msgIAddressObject>& recipients,
622     lpnsMapiRecipDesc mapiRecips, int mapiRecipClass) {
623   nsTArray<nsCString> names, addresses;
624   ExtractAllAddresses(recipients, UTF16ArrayAdapter<>(names),
625                       UTF16ArrayAdapter<>(addresses));
626 
627   size_t numAddresses = names.Length();
628   for (size_t i = 0; i < numAddresses; i++) {
629     if (!names[i].IsEmpty()) {
630       mapiRecips[i].lpszName = (char*)CoTaskMemAlloc(names[i].Length() + 1);
631       if (mapiRecips[i].lpszName)
632         strcpy((char*)mapiRecips[i].lpszName, names[i].get());
633     }
634     if (!addresses[i].IsEmpty()) {
635       mapiRecips[i].lpszName = (char*)CoTaskMemAlloc(addresses[i].Length() + 1);
636       if (mapiRecips[i].lpszName)
637         strcpy((char*)mapiRecips[i].lpszName, addresses[i].get());
638     }
639     mapiRecips[i].ulRecipClass = mapiRecipClass;
640   }
641 }
642 
ConvertBodyToMapiFormat(nsIMsgDBHdr * hdr)643 char* MsgMapiListContext::ConvertBodyToMapiFormat(nsIMsgDBHdr* hdr) {
644   const int kBufLen =
645       64000;  // I guess we only return the first 64K of a message.
646 #define EMPTY_MESSAGE_LINE(buf) \
647   (buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0')
648 
649   nsCOMPtr<nsIMsgFolder> folder;
650   hdr->GetFolder(getter_AddRefs(folder));
651   if (!folder) return nullptr;
652 
653   nsCOMPtr<nsIFile> localFile;
654   folder->GetFilePath(getter_AddRefs(localFile));
655 
656   nsresult rv;
657   nsCOMPtr<nsIFileInputStream> fileStream =
658       do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
659   NS_ENSURE_SUCCESS(rv, nullptr);
660 
661   rv = fileStream->Init(localFile, PR_RDONLY, 0664,
662                         false);  // just have to read the messages
663   NS_ENSURE_SUCCESS(rv, nullptr);
664 
665   nsCOMPtr<nsILineInputStream> fileLineStream = do_QueryInterface(fileStream);
666   if (!fileLineStream) return nullptr;
667 
668   // ### really want to skip past headers...
669   uint64_t messageOffset;
670   uint32_t lineCount;
671   hdr->GetMessageOffset(&messageOffset);
672   hdr->GetLineCount(&lineCount);
673   nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(fileStream);
674   seekableStream->Seek(PR_SEEK_SET, messageOffset);
675   bool hasMore = true;
676   nsAutoCString curLine;
677 
678   while (hasMore)  // advance past message headers
679   {
680     nsresult rv = fileLineStream->ReadLine(curLine, &hasMore);
681     if (NS_FAILED(rv) || EMPTY_MESSAGE_LINE(curLine)) break;
682   }
683   uint32_t msgSize;
684   hdr->GetMessageSize(&msgSize);
685   if (msgSize > kBufLen) msgSize = kBufLen - 1;
686   // this is too big, since it includes the msg hdr size...oh well
687   char* body = (char*)CoTaskMemAlloc(msgSize + 1);
688 
689   if (!body) return nullptr;
690   int32_t bytesCopied = 0;
691   for (hasMore = TRUE; lineCount > 0 && hasMore && NS_SUCCEEDED(rv);
692        lineCount--) {
693     rv = fileLineStream->ReadLine(curLine, &hasMore);
694     if (NS_FAILED(rv)) break;
695     curLine.Append(CRLF);
696     // make sure we have room left
697     if (bytesCopied + curLine.Length() < msgSize) {
698       strcpy(body + bytesCopied, curLine.get());
699       bytesCopied += curLine.Length();
700     }
701   }
702   MOZ_LOG(MAPI, mozilla::LogLevel::Debug,
703           ("ConvertBodyToMapiFormat size=%x allocated size %x body = %100.100s",
704            bytesCopied, msgSize + 1, (char*)body));
705   body[bytesCopied] = '\0';  // rhp - fix last line garbage...
706   return body;
707 }
708 
709 //*****************************************************************************
710 // MSGMAPI API implementation
711 
msg_FreeMAPIFile(lpMapiFileDesc f)712 static void msg_FreeMAPIFile(lpMapiFileDesc f) {
713   if (f) {
714     CoTaskMemFree(f->lpszPathName);
715     CoTaskMemFree(f->lpszFileName);
716   }
717 }
718 
msg_FreeMAPIRecipient(lpMapiRecipDesc rd)719 static void msg_FreeMAPIRecipient(lpMapiRecipDesc rd) {
720   if (rd) {
721     if (rd->lpszName) CoTaskMemFree(rd->lpszName);
722     if (rd->lpszAddress) CoTaskMemFree(rd->lpszAddress);
723     // CoTaskMemFree(rd->lpEntryID);
724   }
725 }
726 
MSG_FreeMapiMessage(lpMapiMessage msg)727 extern "C" void MSG_FreeMapiMessage(lpMapiMessage msg) {
728   ULONG i;
729 
730   if (msg) {
731     CoTaskMemFree(msg->lpszSubject);
732     CoTaskMemFree(msg->lpszNoteText);
733     CoTaskMemFree(msg->lpszMessageType);
734     CoTaskMemFree(msg->lpszDateReceived);
735     CoTaskMemFree(msg->lpszConversationID);
736 
737     if (msg->lpOriginator) msg_FreeMAPIRecipient(msg->lpOriginator);
738 
739     for (i = 0; i < msg->nRecipCount; i++)
740       if (&(msg->lpRecips[i]) != nullptr)
741         msg_FreeMAPIRecipient(&(msg->lpRecips[i]));
742 
743     CoTaskMemFree(msg->lpRecips);
744 
745     for (i = 0; i < msg->nFileCount; i++)
746       if (&(msg->lpFiles[i]) != nullptr) msg_FreeMAPIFile(&(msg->lpFiles[i]));
747 
748     CoTaskMemFree(msg->lpFiles);
749 
750     CoTaskMemFree(msg);
751   }
752 }
753 
MsgMarkMapiMessageRead(nsIMsgFolder * folder,nsMsgKey key,bool read)754 extern "C" bool MsgMarkMapiMessageRead(nsIMsgFolder* folder, nsMsgKey key,
755                                        bool read) {
756   bool success = FALSE;
757   MsgMapiListContext* context = new MsgMapiListContext();
758   if (context) {
759     if (NS_SUCCEEDED(context->OpenDatabase(folder))) {
760       if (NS_SUCCEEDED(context->MarkRead(key, read))) success = TRUE;
761     }
762     delete context;
763   }
764   return success;
765 }
766 
DeleteMessage(nsMsgKey key)767 bool MsgMapiListContext::DeleteMessage(nsMsgKey key) {
768   if (!m_db) return FALSE;
769 
770   if (!IsIMAPHost()) {
771     nsTArray<nsMsgKey> doomed({key});
772     return NS_SUCCEEDED((m_db->DeleteMessages(doomed, nullptr)));
773   }
774 #if 0
775   else if ( m_folder->GetIMAPFolderInfoMail() )
776   {
777     AutoTArray<nsMsgKey, 1> messageKeys;
778     messageKeys.AppendElement(key);
779 
780     (m_folder->GetIMAPFolderInfoMail())->DeleteSpecifiedMessages(pane, messageKeys, nsMsgKey_None);
781     m_db->DeleteMessage(key, nullptr, FALSE);
782     return TRUE;
783   }
784 #endif
785   else {
786     return FALSE;
787   }
788 }
789 
790 /* Return TRUE on success, FALSE on failure */
MSG_DeleteMapiMessage(nsIMsgFolder * folder,nsMsgKey key)791 extern "C" bool MSG_DeleteMapiMessage(nsIMsgFolder* folder, nsMsgKey key) {
792   bool success = FALSE;
793   MsgMapiListContext* context = new MsgMapiListContext();
794   if (context) {
795     if (NS_SUCCEEDED(context->OpenDatabase(folder))) {
796       success = context->DeleteMessage(key);
797     }
798 
799     delete context;
800   }
801 
802   return success;
803 }
804