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 
6 #include "msgCore.h"
7 #include "nsUnicharUtils.h"
8 #include "nsMsgDBFolder.h"
9 #include "nsMsgFolderFlags.h"
10 #include "nsIPrefBranch.h"
11 #include "nsIPrefService.h"
12 #include "nsNetUtil.h"
13 #include "nsIMsgFolderCache.h"
14 #include "nsIMsgFolderCacheElement.h"
15 #include "nsMsgBaseCID.h"
16 #include "nsIMsgMailNewsUrl.h"
17 #include "nsMsgDatabase.h"
18 #include "nsIMsgAccountManager.h"
19 #include "nsISeekableStream.h"
20 #include "nsNativeCharsetUtils.h"
21 #include "nsIChannel.h"
22 #include "nsITransport.h"
23 #include "nsIWindowWatcher.h"
24 #include "nsIMsgFolderCompactor.h"
25 #include "nsIDocShell.h"
26 #include "nsIMsgWindow.h"
27 #include "nsIPrompt.h"
28 #include "nsIInterfaceRequestor.h"
29 #include "nsIInterfaceRequestorUtils.h"
30 #include "nsCollationCID.h"
31 #include "nsAbBaseCID.h"
32 #include "nsIAbCard.h"
33 #include "nsIAbDirectory.h"
34 #include "nsISpamSettings.h"
35 #include "nsIMsgFilterPlugin.h"
36 #include "nsIMsgMailSession.h"
37 #include "nsTextFormatter.h"
38 #include "nsMsgDBCID.h"
39 #include "nsReadLine.h"
40 #include "nsLayoutCID.h"
41 #include "nsIParserUtils.h"
42 #include "nsIDocumentEncoder.h"
43 #include "nsMsgI18N.h"
44 #include "nsIMIMEHeaderParam.h"
45 #include "plbase64.h"
46 #include <time.h>
47 #include "nsIMsgFolderNotificationService.h"
48 #include "nsIMimeHeaders.h"
49 #include "nsDirectoryServiceDefs.h"
50 #include "nsIMsgTraitService.h"
51 #include "nsIMessenger.h"
52 #include "nsThreadUtils.h"
53 #include "nsITransactionManager.h"
54 #include "nsMsgReadStateTxn.h"
55 #include "prmem.h"
56 #include "nsIPK11TokenDB.h"
57 #include "nsIPK11Token.h"
58 #include "nsMsgLocalFolderHdrs.h"
59 #define oneHour 3600000000U
60 #include "nsMsgUtils.h"
61 #include "nsIMsgFilterService.h"
62 #include "nsDirectoryServiceUtils.h"
63 #include "nsMimeTypes.h"
64 #include "nsIMsgFilter.h"
65 #include "nsIScriptError.h"
66 #include "nsIURIMutator.h"
67 #include "nsIXULAppInfo.h"
68 #include "mozilla/Services.h"
69 #include "mozilla/intl/LocaleService.h"
70 #include "mozilla/Logging.h"
71 #include "mozilla/UniquePtr.h"
72 #include "mozilla/Utf8.h"
73 
74 using namespace mozilla;
75 
76 extern LazyLogModule FILTERLOGMODULE;
77 extern LazyLogModule DBLog;
78 
79 static PRTime gtimeOfLastPurgeCheck;  // variable to know when to check for
80                                       // purge threshold
81 
82 #define PREF_MAIL_PROMPT_PURGE_THRESHOLD "mail.prompt_purge_threshhold"
83 #define PREF_MAIL_PURGE_THRESHOLD "mail.purge_threshhold"
84 #define PREF_MAIL_PURGE_THRESHOLD_MB "mail.purge_threshhold_mb"
85 #define PREF_MAIL_PURGE_MIGRATED "mail.purge_threshold_migrated"
86 #define PREF_MAIL_PURGE_ASK "mail.purge.ask"
87 #define PREF_MAIL_WARN_FILTER_CHANGED "mail.warn_filter_changed"
88 
89 const char* kUseServerRetentionProp = "useServerRetention";
90 
NS_IMPL_ISUPPORTS(nsMsgFolderService,nsIMsgFolderService)91 NS_IMPL_ISUPPORTS(nsMsgFolderService, nsIMsgFolderService)
92 
93 // This method serves the only purpose to re-initialize the
94 // folder name strings when UI initialization is done.
95 // XXX TODO: This can be removed when the localization system gets
96 // initialized in M-C code before, for example, the permission manager
97 // triggers folder creation during imap: URI creation.
98 // In fact, the entire class together with nsMsgDBFolder::FolderNamesReady()
99 // can be removed.
100 NS_IMETHODIMP nsMsgFolderService::InitializeFolderStrings() {
101   nsMsgDBFolder::initializeStrings();
102   nsMsgDBFolder::gInitializeStringsDone = true;
103   return NS_OK;
104 }
105 
106 nsICollation* nsMsgDBFolder::gCollationKeyGenerator = nullptr;
107 
108 nsString nsMsgDBFolder::kLocalizedInboxName;
109 nsString nsMsgDBFolder::kLocalizedTrashName;
110 nsString nsMsgDBFolder::kLocalizedSentName;
111 nsString nsMsgDBFolder::kLocalizedDraftsName;
112 nsString nsMsgDBFolder::kLocalizedTemplatesName;
113 nsString nsMsgDBFolder::kLocalizedUnsentName;
114 nsString nsMsgDBFolder::kLocalizedJunkName;
115 nsString nsMsgDBFolder::kLocalizedArchivesName;
116 
117 nsString nsMsgDBFolder::kLocalizedBrandShortName;
118 
119 nsrefcnt nsMsgDBFolder::mInstanceCount = 0;
120 bool nsMsgDBFolder::gInitializeStringsDone = false;
121 
122 // We define strings for folder properties and events.
123 // Properties:
124 constexpr nsLiteralCString kBiffState = "BiffState"_ns;
125 constexpr nsLiteralCString kCanFileMessages = "CanFileMessages"_ns;
126 constexpr nsLiteralCString kDefaultServer = "DefaultServer"_ns;
127 constexpr nsLiteralCString kFlagged = "Flagged"_ns;
128 constexpr nsLiteralCString kFolderFlag = "FolderFlag"_ns;
129 constexpr nsLiteralCString kFolderSize = "FolderSize"_ns;
130 constexpr nsLiteralCString kIsDeferred = "isDeferred"_ns;
131 constexpr nsLiteralCString kIsSecure = "isSecure"_ns;
132 constexpr nsLiteralCString kJunkStatusChanged = "JunkStatusChanged"_ns;
133 constexpr nsLiteralCString kKeywords = "Keywords"_ns;
134 constexpr nsLiteralCString kMRMTimeChanged = "MRMTimeChanged"_ns;
135 constexpr nsLiteralCString kMsgLoaded = "msgLoaded"_ns;
136 constexpr nsLiteralCString kName = "Name"_ns;
137 constexpr nsLiteralCString kNewMailReceived = "NewMailReceived"_ns;
138 constexpr nsLiteralCString kNewMessages = "NewMessages"_ns;
139 constexpr nsLiteralCString kOpen = "open"_ns;
140 constexpr nsLiteralCString kSortOrder = "SortOrder"_ns;
141 constexpr nsLiteralCString kStatus = "Status"_ns;
142 constexpr nsLiteralCString kSynchronize = "Synchronize"_ns;
143 constexpr nsLiteralCString kTotalMessages = "TotalMessages"_ns;
144 constexpr nsLiteralCString kTotalUnreadMessages = "TotalUnreadMessages"_ns;
145 
146 // Events:
147 constexpr nsLiteralCString kAboutToCompact = "AboutToCompact"_ns;
148 constexpr nsLiteralCString kCompactCompleted = "CompactCompleted"_ns;
149 constexpr nsLiteralCString kDeleteOrMoveMsgCompleted =
150     "DeleteOrMoveMsgCompleted"_ns;
151 constexpr nsLiteralCString kDeleteOrMoveMsgFailed = "DeleteOrMoveMsgFailed"_ns;
152 constexpr nsLiteralCString kFiltersApplied = "FiltersApplied"_ns;
153 constexpr nsLiteralCString kFolderCreateCompleted = "FolderCreateCompleted"_ns;
154 constexpr nsLiteralCString kFolderCreateFailed = "FolderCreateFailed"_ns;
155 constexpr nsLiteralCString kFolderLoaded = "FolderLoaded"_ns;
156 constexpr nsLiteralCString kNumNewBiffMessages = "NumNewBiffMessages"_ns;
157 constexpr nsLiteralCString kRenameCompleted = "RenameCompleted"_ns;
158 
NS_IMPL_ISUPPORTS(nsMsgDBFolder,nsISupportsWeakReference,nsIMsgFolder,nsIDBChangeListener,nsIUrlListener,nsIJunkMailClassificationListener,nsIMsgTraitClassificationListener)159 NS_IMPL_ISUPPORTS(nsMsgDBFolder, nsISupportsWeakReference, nsIMsgFolder,
160                   nsIDBChangeListener, nsIUrlListener,
161                   nsIJunkMailClassificationListener,
162                   nsIMsgTraitClassificationListener)
163 
164 nsMsgDBFolder::nsMsgDBFolder(void)
165     : mAddListener(true),
166       mNewMessages(false),
167       mGettingNewMessages(false),
168       mLastMessageLoaded(nsMsgKey_None),
169       mFlags(0),
170       mNumUnreadMessages(-1),
171       mNumTotalMessages(-1),
172       mNotifyCountChanges(true),
173       mExpungedBytes(0),
174       mInitializedFromCache(false),
175       mSemaphoreHolder(nullptr),
176       mNumPendingUnreadMessages(0),
177       mNumPendingTotalMessages(0),
178       mFolderSize(kSizeUnknown),
179       mNumNewBiffMessages(0),
180       mHaveParsedURI(false),
181       mIsServerIsValid(false),
182       mIsServer(false) {
183   if (mInstanceCount++ <= 0) {
184     initializeStrings();
185 
186     do {
187       nsresult rv;
188       // We need to check whether we're running under xpcshell,
189       // in that case, we always assume that the strings are good.
190       // XXX TODO: This hack can be removed when the localization system gets
191       // initialized in M-C code before, for example, the permission manager
192       // triggers folder creation during imap: URI creation.
193       nsCOMPtr<nsIXULAppInfo> appinfo =
194           do_GetService("@mozilla.org/xre/app-info;1", &rv);
195       if (NS_FAILED(rv)) break;
196       nsAutoCString appName;
197       rv = appinfo->GetName(appName);
198       if (NS_FAILED(rv)) break;
199       if (appName.Equals("xpcshell")) gInitializeStringsDone = true;
200     } while (false);
201 
202     createCollationKeyGenerator();
203     gtimeOfLastPurgeCheck = 0;
204   }
205 
206   mProcessingFlag[0].bit = nsMsgProcessingFlags::ClassifyJunk;
207   mProcessingFlag[1].bit = nsMsgProcessingFlags::ClassifyTraits;
208   mProcessingFlag[2].bit = nsMsgProcessingFlags::TraitsDone;
209   mProcessingFlag[3].bit = nsMsgProcessingFlags::FiltersDone;
210   mProcessingFlag[4].bit = nsMsgProcessingFlags::FilterToMove;
211   mProcessingFlag[5].bit = nsMsgProcessingFlags::NotReportedClassified;
212   for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
213     mProcessingFlag[i].keys = nsMsgKeySetU::Create();
214 }
215 
~nsMsgDBFolder(void)216 nsMsgDBFolder::~nsMsgDBFolder(void) {
217   for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
218     delete mProcessingFlag[i].keys;
219 
220   if (--mInstanceCount == 0) {
221     NS_IF_RELEASE(gCollationKeyGenerator);
222   }
223   // shutdown but don't shutdown children.
224   Shutdown(false);
225 }
226 
FolderNamesReady(bool * aReady)227 NS_IMETHODIMP nsMsgDBFolder::FolderNamesReady(bool* aReady) {
228   *aReady = gInitializeStringsDone;
229   return NS_OK;
230 }
231 
Shutdown(bool shutdownChildren)232 NS_IMETHODIMP nsMsgDBFolder::Shutdown(bool shutdownChildren) {
233   if (mDatabase) {
234     mDatabase->RemoveListener(this);
235     mDatabase->ForceClosed();
236     mDatabase = nullptr;
237     if (mBackupDatabase) {
238       mBackupDatabase->ForceClosed();
239       mBackupDatabase = nullptr;
240     }
241   }
242 
243   if (shutdownChildren) {
244     int32_t count = mSubFolders.Count();
245 
246     for (int32_t i = 0; i < count; i++) mSubFolders[i]->Shutdown(true);
247 
248     // Reset incoming server pointer and pathname.
249     mServer = nullptr;
250     mPath = nullptr;
251     mHaveParsedURI = false;
252     mName.Truncate();
253     mSubFolders.Clear();
254   }
255   return NS_OK;
256 }
257 
ForceDBClosed()258 NS_IMETHODIMP nsMsgDBFolder::ForceDBClosed() {
259   int32_t count = mSubFolders.Count();
260   for (int32_t i = 0; i < count; i++) mSubFolders[i]->ForceDBClosed();
261 
262   if (mDatabase) {
263     mDatabase->ForceClosed();
264     mDatabase = nullptr;
265   } else {
266     nsCOMPtr<nsIMsgDBService> mailDBFactory(
267         do_GetService(NS_MSGDB_SERVICE_CONTRACTID));
268     if (mailDBFactory) mailDBFactory->ForceFolderDBClosed(this);
269   }
270   return NS_OK;
271 }
272 
CloseAndBackupFolderDB(const nsACString & newName)273 NS_IMETHODIMP nsMsgDBFolder::CloseAndBackupFolderDB(const nsACString& newName) {
274   ForceDBClosed();
275 
276   // We only support backup for mail at the moment
277   if (!(mFlags & nsMsgFolderFlags::Mail)) return NS_OK;
278 
279   nsCOMPtr<nsIFile> folderPath;
280   nsresult rv = GetFilePath(getter_AddRefs(folderPath));
281   NS_ENSURE_SUCCESS(rv, rv);
282 
283   nsCOMPtr<nsIFile> dbFile;
284   rv = GetSummaryFileLocation(folderPath, getter_AddRefs(dbFile));
285   NS_ENSURE_SUCCESS(rv, rv);
286 
287   nsCOMPtr<nsIFile> backupDir;
288   rv = CreateBackupDirectory(getter_AddRefs(backupDir));
289   NS_ENSURE_SUCCESS(rv, rv);
290 
291   nsCOMPtr<nsIFile> backupDBFile;
292   rv = GetBackupSummaryFile(getter_AddRefs(backupDBFile), newName);
293   NS_ENSURE_SUCCESS(rv, rv);
294 
295   if (mBackupDatabase) {
296     mBackupDatabase->ForceClosed();
297     mBackupDatabase = nullptr;
298   }
299 
300   backupDBFile->Remove(false);
301   bool backupExists;
302   backupDBFile->Exists(&backupExists);
303   NS_ASSERTION(!backupExists, "Couldn't delete database backup");
304   if (backupExists) return NS_ERROR_FAILURE;
305 
306   if (!newName.IsEmpty()) {
307     nsAutoCString backupName;
308     rv = backupDBFile->GetNativeLeafName(backupName);
309     NS_ENSURE_SUCCESS(rv, rv);
310     return dbFile->CopyToNative(backupDir, backupName);
311   } else
312     return dbFile->CopyToNative(backupDir, EmptyCString());
313 }
314 
OpenBackupMsgDatabase()315 NS_IMETHODIMP nsMsgDBFolder::OpenBackupMsgDatabase() {
316   if (mBackupDatabase) return NS_OK;
317   nsCOMPtr<nsIFile> folderPath;
318   nsresult rv = GetFilePath(getter_AddRefs(folderPath));
319   NS_ENSURE_SUCCESS(rv, rv);
320 
321   nsAutoString folderName;
322   rv = folderPath->GetLeafName(folderName);
323   NS_ENSURE_SUCCESS(rv, rv);
324 
325   nsCOMPtr<nsIFile> backupDir;
326   rv = CreateBackupDirectory(getter_AddRefs(backupDir));
327   NS_ENSURE_SUCCESS(rv, rv);
328 
329   // We use a dummy message folder file so we can use
330   // GetSummaryFileLocation to get the db file name
331   nsCOMPtr<nsIFile> backupDBDummyFolder;
332   rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
333   NS_ENSURE_SUCCESS(rv, rv);
334   rv = backupDBDummyFolder->Append(folderName);
335   NS_ENSURE_SUCCESS(rv, rv);
336 
337   nsCOMPtr<nsIMsgDBService> msgDBService =
338       do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
339   NS_ENSURE_SUCCESS(rv, rv);
340   rv = msgDBService->OpenMailDBFromFile(backupDBDummyFolder, this, false, true,
341                                         getter_AddRefs(mBackupDatabase));
342   // we add a listener so that we can close the db during OnAnnouncerGoingAway.
343   // There should not be any other calls to the listener with the backup
344   // database
345   if (NS_SUCCEEDED(rv) && mBackupDatabase) mBackupDatabase->AddListener(this);
346 
347   if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
348     // this is normal in reparsing
349     rv = NS_OK;
350   return rv;
351 }
352 
RemoveBackupMsgDatabase()353 NS_IMETHODIMP nsMsgDBFolder::RemoveBackupMsgDatabase() {
354   nsCOMPtr<nsIFile> folderPath;
355   nsresult rv = GetFilePath(getter_AddRefs(folderPath));
356   NS_ENSURE_SUCCESS(rv, rv);
357 
358   nsAutoString folderName;
359   rv = folderPath->GetLeafName(folderName);
360   NS_ENSURE_SUCCESS(rv, rv);
361 
362   nsCOMPtr<nsIFile> backupDir;
363   rv = CreateBackupDirectory(getter_AddRefs(backupDir));
364   NS_ENSURE_SUCCESS(rv, rv);
365 
366   // We use a dummy message folder file so we can use
367   // GetSummaryFileLocation to get the db file name
368   nsCOMPtr<nsIFile> backupDBDummyFolder;
369   rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
370   NS_ENSURE_SUCCESS(rv, rv);
371   rv = backupDBDummyFolder->Append(folderName);
372   NS_ENSURE_SUCCESS(rv, rv);
373 
374   nsCOMPtr<nsIFile> backupDBFile;
375   rv =
376       GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile));
377   NS_ENSURE_SUCCESS(rv, rv);
378 
379   if (mBackupDatabase) {
380     mBackupDatabase->ForceClosed();
381     mBackupDatabase = nullptr;
382   }
383 
384   return backupDBFile->Remove(false);
385 }
386 
StartFolderLoading(void)387 NS_IMETHODIMP nsMsgDBFolder::StartFolderLoading(void) {
388   if (mDatabase) mDatabase->RemoveListener(this);
389   mAddListener = false;
390   return NS_OK;
391 }
392 
EndFolderLoading(void)393 NS_IMETHODIMP nsMsgDBFolder::EndFolderLoading(void) {
394   if (mDatabase) mDatabase->AddListener(this);
395   mAddListener = true;
396   UpdateSummaryTotals(true);
397 
398   // GGGG       check for new mail here and call SetNewMessages...?? -- ONE OF
399   // THE 2 PLACES
400   if (mDatabase) m_newMsgs.Clear();
401 
402   return NS_OK;
403 }
404 
405 NS_IMETHODIMP
GetExpungedBytes(int64_t * count)406 nsMsgDBFolder::GetExpungedBytes(int64_t* count) {
407   NS_ENSURE_ARG_POINTER(count);
408 
409   if (mDatabase) {
410     nsresult rv;
411     nsCOMPtr<nsIDBFolderInfo> folderInfo;
412     rv = mDatabase->GetDBFolderInfo(getter_AddRefs(folderInfo));
413     if (NS_FAILED(rv)) return rv;
414     rv = folderInfo->GetExpungedBytes(count);
415     if (NS_SUCCEEDED(rv)) mExpungedBytes = *count;  // sync up with the database
416     return rv;
417   } else {
418     ReadDBFolderInfo(false);
419     *count = mExpungedBytes;
420   }
421   return NS_OK;
422 }
423 
GetHasNewMessages(bool * hasNewMessages)424 NS_IMETHODIMP nsMsgDBFolder::GetHasNewMessages(bool* hasNewMessages) {
425   NS_ENSURE_ARG_POINTER(hasNewMessages);
426   *hasNewMessages = mNewMessages;
427   return NS_OK;
428 }
429 
SetHasNewMessages(bool curNewMessages)430 NS_IMETHODIMP nsMsgDBFolder::SetHasNewMessages(bool curNewMessages) {
431   if (curNewMessages != mNewMessages) {
432     // Only change mru time if we're going from doesn't have new to has new.
433     // technically, we should probably update mru time for every new message
434     // but we would pay a performance penalty for that. If the user
435     // opens the folder, the mrutime will get updated anyway.
436     if (curNewMessages) SetMRUTime();
437     bool oldNewMessages = mNewMessages;
438     mNewMessages = curNewMessages;
439     NotifyBoolPropertyChanged(kNewMessages, oldNewMessages, curNewMessages);
440   }
441 
442   return NS_OK;
443 }
444 
GetHasFolderOrSubfolderNewMessages(bool * aResult)445 NS_IMETHODIMP nsMsgDBFolder::GetHasFolderOrSubfolderNewMessages(bool* aResult) {
446   NS_ENSURE_ARG_POINTER(aResult);
447   bool hasNewMessages = mNewMessages;
448 
449   if (!hasNewMessages) {
450     int32_t count = mSubFolders.Count();
451     for (int32_t i = 0; i < count; i++) {
452       bool hasNew = false;
453       mSubFolders[i]->GetHasFolderOrSubfolderNewMessages(&hasNew);
454       if (hasNew) {
455         hasNewMessages = true;
456         break;
457       }
458     }
459   }
460 
461   *aResult = hasNewMessages;
462   return NS_OK;
463 }
464 
GetGettingNewMessages(bool * gettingNewMessages)465 NS_IMETHODIMP nsMsgDBFolder::GetGettingNewMessages(bool* gettingNewMessages) {
466   NS_ENSURE_ARG_POINTER(gettingNewMessages);
467   *gettingNewMessages = mGettingNewMessages;
468   return NS_OK;
469 }
470 
SetGettingNewMessages(bool gettingNewMessages)471 NS_IMETHODIMP nsMsgDBFolder::SetGettingNewMessages(bool gettingNewMessages) {
472   mGettingNewMessages = gettingNewMessages;
473   return NS_OK;
474 }
475 
GetFirstNewMessage(nsIMsgDBHdr ** firstNewMessage)476 NS_IMETHODIMP nsMsgDBFolder::GetFirstNewMessage(nsIMsgDBHdr** firstNewMessage) {
477   // If there's not a db then there can't be new messages.  Return failure since
478   // you should use HasNewMessages first.
479   if (!mDatabase) return NS_ERROR_FAILURE;
480 
481   nsresult rv;
482   nsMsgKey key;
483   rv = mDatabase->GetFirstNew(&key);
484   if (NS_FAILED(rv)) return rv;
485 
486   nsCOMPtr<nsIMsgDBHdr> hdr;
487   rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr));
488   if (NS_FAILED(rv)) return rv;
489 
490   return mDatabase->GetMsgHdrForKey(key, firstNewMessage);
491 }
492 
ClearNewMessages()493 NS_IMETHODIMP nsMsgDBFolder::ClearNewMessages() {
494   nsresult rv = NS_OK;
495   bool dbWasCached = mDatabase != nullptr;
496   if (!dbWasCached) GetDatabase();
497 
498   if (mDatabase) {
499     mDatabase->GetNewList(m_saveNewMsgs);
500     mDatabase->ClearNewList(true);
501   }
502   if (!dbWasCached) SetMsgDatabase(nullptr);
503 
504   m_newMsgs.Clear();
505   mNumNewBiffMessages = 0;
506   return rv;
507 }
508 
UpdateNewMessages()509 void nsMsgDBFolder::UpdateNewMessages() {
510   if (!(mFlags & nsMsgFolderFlags::Virtual)) {
511     bool hasNewMessages = false;
512     for (uint32_t keyIndex = 0; keyIndex < m_newMsgs.Length(); keyIndex++) {
513       bool containsKey = false;
514       mDatabase->ContainsKey(m_newMsgs[keyIndex], &containsKey);
515       if (!containsKey) continue;
516       bool isRead = false;
517       nsresult rv2 = mDatabase->IsRead(m_newMsgs[keyIndex], &isRead);
518       if (NS_SUCCEEDED(rv2) && !isRead) {
519         hasNewMessages = true;
520         mDatabase->AddToNewList(m_newMsgs[keyIndex]);
521       }
522     }
523     SetHasNewMessages(hasNewMessages);
524   }
525 }
526 
527 // helper function that gets the cache element that corresponds to the passed in
528 // file spec. This could be static, or could live in another class - it's not
529 // specific to the current nsMsgDBFolder. If it lived at a higher level, we
530 // could cache the account manager and folder cache.
GetFolderCacheElemFromFile(nsIFile * file,nsIMsgFolderCacheElement ** cacheElement)531 nsresult nsMsgDBFolder::GetFolderCacheElemFromFile(
532     nsIFile* file, nsIMsgFolderCacheElement** cacheElement) {
533   nsresult result;
534   NS_ENSURE_ARG_POINTER(file);
535   NS_ENSURE_ARG_POINTER(cacheElement);
536   nsCOMPtr<nsIMsgFolderCache> folderCache;
537 #ifdef DEBUG_bienvenu1
538   bool exists;
539   NS_ASSERTION(NS_SUCCEEDED(fileSpec->Exists(&exists)) && exists,
540                "whoops, file doesn't exist, mac will break");
541 #endif
542   nsCOMPtr<nsIMsgAccountManager> accountMgr =
543       do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &result);
544   if (NS_SUCCEEDED(result)) {
545     result = accountMgr->GetFolderCache(getter_AddRefs(folderCache));
546     if (NS_SUCCEEDED(result) && folderCache) {
547       nsCString persistentPath;
548       result = file->GetPersistentDescriptor(persistentPath);
549       NS_ENSURE_SUCCESS(result, result);
550       result =
551           folderCache->GetCacheElement(persistentPath, false, cacheElement);
552     }
553   }
554   return result;
555 }
556 
ReadDBFolderInfo(bool force)557 nsresult nsMsgDBFolder::ReadDBFolderInfo(bool force) {
558   // Since it turns out to be pretty expensive to open and close
559   // the DBs all the time, if we have to open it once, get everything
560   // we might need while we're here
561   nsresult result = NS_OK;
562 
563   // don't need to reload from cache if we've already read from cache,
564   // and, we might get stale info, so don't do it.
565   if (!mInitializedFromCache) {
566     // This path is not used to open a file. Instead, it's used as a key into
567     // the foldercache.
568     nsCOMPtr<nsIFile> dbPath;
569     result =
570         GetFolderCacheKey(getter_AddRefs(dbPath), true /* createDBIfMissing */);
571     if (dbPath) {
572       nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
573       result = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
574       if (NS_SUCCEEDED(result) && cacheElement) {
575         if (NS_SUCCEEDED(ReadFromFolderCacheElem(cacheElement))) {
576           mInitializedFromCache = true;
577         }
578       }
579     }
580   }
581 
582   if (force || !mInitializedFromCache) {
583     nsCOMPtr<nsIDBFolderInfo> folderInfo;
584     nsCOMPtr<nsIMsgDatabase> db;
585     result =
586         GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
587     if (NS_SUCCEEDED(result)) {
588       if (folderInfo) {
589         if (!mInitializedFromCache) {
590           folderInfo->GetFlags((int32_t*)&mFlags);
591 #ifdef DEBUG_bienvenu1
592           nsString name;
593           GetName(name);
594           NS_ASSERTION(Compare(name, kLocalizedTrashName) ||
595                            (mFlags & nsMsgFolderFlags::Trash),
596                        "lost trash flag");
597 #endif
598           mInitializedFromCache = true;
599         }
600 
601         folderInfo->GetNumMessages(&mNumTotalMessages);
602         folderInfo->GetNumUnreadMessages(&mNumUnreadMessages);
603         folderInfo->GetExpungedBytes(&mExpungedBytes);
604 
605         nsCString utf8Name;
606         folderInfo->GetFolderName(utf8Name);
607         if (!utf8Name.IsEmpty()) CopyUTF8toUTF16(utf8Name, mName);
608 
609         // These should be put in IMAP folder only.
610         // folderInfo->GetImapTotalPendingMessages(&mNumPendingTotalMessages);
611         // folderInfo->GetImapUnreadPendingMessages(&mNumPendingUnreadMessages);
612 
613         if (db) {
614           bool hasnew;
615           nsresult rv;
616           rv = db->HasNew(&hasnew);
617           if (NS_FAILED(rv)) return rv;
618         }
619       }
620     } else {
621       // we tried to open DB but failed - don't keep trying.
622       // If a DB is created, we will call this method with force == TRUE,
623       // and read from the db that way.
624       mInitializedFromCache = true;
625     }
626   }
627   return result;
628 }
629 
SendFlagNotifications(nsIMsgDBHdr * item,uint32_t oldFlags,uint32_t newFlags)630 nsresult nsMsgDBFolder::SendFlagNotifications(nsIMsgDBHdr* item,
631                                               uint32_t oldFlags,
632                                               uint32_t newFlags) {
633   nsresult rv = NS_OK;
634   uint32_t changedFlags = oldFlags ^ newFlags;
635   if ((changedFlags & nsMsgMessageFlags::Read) &&
636       (changedFlags & nsMsgMessageFlags::New)) {
637     //..so..if the msg is read in the folder and the folder has new msgs clear
638     // the account level and status bar biffs.
639     rv = NotifyPropertyFlagChanged(item, kStatus, oldFlags, newFlags);
640     rv = SetBiffState(nsMsgBiffState_NoMail);
641   } else if (changedFlags &
642              (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |
643               nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::IMAPDeleted |
644               nsMsgMessageFlags::New | nsMsgMessageFlags::Offline))
645     rv = NotifyPropertyFlagChanged(item, kStatus, oldFlags, newFlags);
646   else if ((changedFlags & nsMsgMessageFlags::Marked))
647     rv = NotifyPropertyFlagChanged(item, kFlagged, oldFlags, newFlags);
648   return rv;
649 }
650 
DownloadMessagesForOffline(nsTArray<RefPtr<nsIMsgDBHdr>> const & messages,nsIMsgWindow *)651 NS_IMETHODIMP nsMsgDBFolder::DownloadMessagesForOffline(
652     nsTArray<RefPtr<nsIMsgDBHdr>> const& messages, nsIMsgWindow*) {
653   NS_ASSERTION(false, "imap and news need to override this");
654   return NS_OK;
655 }
656 
DownloadAllForOffline(nsIUrlListener * listener,nsIMsgWindow * msgWindow)657 NS_IMETHODIMP nsMsgDBFolder::DownloadAllForOffline(nsIUrlListener* listener,
658                                                    nsIMsgWindow* msgWindow) {
659   NS_ASSERTION(false, "imap and news need to override this");
660   return NS_OK;
661 }
662 
GetMsgStore(nsIMsgPluggableStore ** aStore)663 NS_IMETHODIMP nsMsgDBFolder::GetMsgStore(nsIMsgPluggableStore** aStore) {
664   NS_ENSURE_ARG_POINTER(aStore);
665   nsCOMPtr<nsIMsgIncomingServer> server;
666   nsresult rv = GetServer(getter_AddRefs(server));
667   NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
668   return server->GetMsgStore(aStore);
669 }
670 
GetOfflineFileStream(nsMsgKey msgKey,int64_t * offset,uint32_t * size,nsIInputStream ** aFileStream)671 NS_IMETHODIMP nsMsgDBFolder::GetOfflineFileStream(
672     nsMsgKey msgKey, int64_t* offset, uint32_t* size,
673     nsIInputStream** aFileStream) {
674   NS_ENSURE_ARG(aFileStream);
675 
676   *offset = *size = 0;
677 
678   nsresult rv = GetDatabase();
679   NS_ENSURE_SUCCESS(rv, rv);
680   nsCOMPtr<nsIMsgDBHdr> hdr;
681   rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
682   NS_ENSURE_SUCCESS(rv, rv);
683   hdr->GetOfflineMessageSize(size);
684 
685   bool reusable;
686   rv = GetMsgInputStream(hdr, &reusable, aFileStream);
687   NS_ENSURE_SUCCESS(rv, rv);
688   // Check if the database has the correct offset into the offline store by
689   // reading up to 300 bytes. If it is incorrect, clear the offline flag on the
690   // message and return false. This will cause a fall back to reading the
691   // message from the server. We will also advance the offset past the envelope
692   // header ("From " or "FCC") and "X-Mozilla-Status*" lines so these line are
693   // not included when the message is read from the file.
694   // Note: This occurs for both mbox and maildir offline store and probably any
695   // future pluggable store that may be supported.
696   nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(*aFileStream);
697   if (seekableStream) {
698     seekableStream->Tell(offset);
699     char startOfMsg[301];
700     uint32_t bytesRead = 0;
701     uint32_t bytesToRead = sizeof(startOfMsg) - 1;
702     rv = (*aFileStream)->Read(startOfMsg, bytesToRead, &bytesRead);
703     startOfMsg[bytesRead] = '\0';
704     uint32_t msgOffset = 0;
705     uint32_t keepMsgOffset = 0;
706     char* headerLine = startOfMsg;
707     int32_t findPos;
708     // Check a few lines in startOfMsg[] to verify message record validity.
709     bool line1 = true;
710     bool foundError = false;
711     // If Read() above fails, don't check any lines and set record bad.
712     // Even if Read() succeeds, don't enter the loop below if bytesRead is 0.
713     bool foundNextLine = NS_SUCCEEDED(rv) && (bytesRead > 0) ? true : false;
714     while (foundNextLine) {
715       headerLine = startOfMsg + msgOffset;
716       // Ignore lines beginning X-Mozilla-Status or X-Mozilla-Status2
717       if (!strncmp(headerLine, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN) ||
718           !strncmp(headerLine, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN)) {
719         // If there is an invalid line ahead of X-Mozilla-Status lines,
720         // immediately flag this a bad record. Only the "From " or "FCC"
721         // delimiter line is expected and OK before this.
722         if (foundError) {
723           break;
724         }
725         foundNextLine =
726             MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
727         line1 = false;
728         continue;
729       }
730       if (line1) {
731         // Ignore "From " and, for Drafts, "FCC" when on first line.
732         if ((!strncmp(headerLine, "From ", 5) ||
733              ((mFlags & nsMsgFolderFlags::Drafts) &&
734               !strncmp(headerLine, "FCC", 3)))) {
735           foundNextLine =
736               MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
737           line1 = false;
738           continue;
739         }
740       }
741       bool validOrFrom = false;
742       // Check if line looks like a valid header (just check for a colon). Also
743       // a line beginning with "From " as is sometimes returned by broken IMAP
744       // servers is also acceptable.
745       findPos = MsgFindCharInSet(nsDependentCString(headerLine), ":\n\r", 0);
746       if ((findPos != kNotFound && headerLine[findPos] == ':') ||
747           !strncmp(headerLine, "From ", 5)) {
748         validOrFrom = true;
749       }
750       if (!foundError) {
751         if (validOrFrom) {
752           // Record looks OK, accept it.
753           break;
754         } else {
755           foundError = true;
756           keepMsgOffset = msgOffset;
757           foundNextLine =
758               MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
759           if (MOZ_LOG_TEST(DBLog, LogLevel::Info)) {
760             char save;
761             if (foundNextLine) {
762               // Temporarily null terminate the bad header line for logging.
763               save = startOfMsg[msgOffset - 1];  // should be \r or \n
764               startOfMsg[msgOffset - 1] = 0;
765             }
766             MOZ_LOG(DBLog, LogLevel::Info,
767                     ("Invalid header line in offline store: %s",
768                      startOfMsg + keepMsgOffset));
769             if (foundNextLine) startOfMsg[msgOffset - 1] = save;
770           }
771           line1 = false;
772           continue;
773         }
774       } else {
775         if (validOrFrom) {
776           // Previous was bad, this is good, accept the record at bad line.
777           foundError = false;
778           msgOffset = keepMsgOffset;
779           break;
780         }
781         // If reached, two consecutive lines bad, reject the record
782         break;
783       }
784     }  // while (foundNextLine)
785 
786     if (!foundNextLine) {
787       // Can't find a valid header line in the buffer or buffer read() failed.
788       foundError = true;
789     }
790 
791     if (!foundError) {
792       *offset += msgOffset;
793       *size -= msgOffset;
794       seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, *offset);
795       MOZ_ASSERT(NS_SUCCEEDED(rv));
796     } else {
797       // Offline store message record looks bad. Cause message fetch from
798       // server and store in RAM cache.
799       MOZ_ASSERT(mDatabase);  // Would have crashed above if mDatabase null!
800       mDatabase->MarkOffline(msgKey, false, nullptr);
801       MOZ_LOG(DBLog, LogLevel::Error,
802               ("Leading offline store file content appears invalid, will fetch "
803                "message from server."));
804       MOZ_LOG(
805           DBLog, LogLevel::Error,
806           ("First 300 bytes of offline store content are:\n%s", startOfMsg));
807       rv = NS_ERROR_FAILURE;
808     }
809   }
810   return rv;
811 }
812 
813 NS_IMETHODIMP
GetOfflineStoreOutputStream(nsIMsgDBHdr * aHdr,nsIOutputStream ** aOutputStream)814 nsMsgDBFolder::GetOfflineStoreOutputStream(nsIMsgDBHdr* aHdr,
815                                            nsIOutputStream** aOutputStream) {
816   NS_ENSURE_ARG_POINTER(aOutputStream);
817   NS_ENSURE_ARG_POINTER(aHdr);
818 
819   nsCOMPtr<nsIMsgPluggableStore> offlineStore;
820   nsresult rv = GetMsgStore(getter_AddRefs(offlineStore));
821   NS_ENSURE_SUCCESS(rv, rv);
822   bool reusable;
823   rv = offlineStore->GetNewMsgOutputStream(this, &aHdr, &reusable,
824                                            aOutputStream);
825   NS_ENSURE_SUCCESS(rv, rv);
826   return rv;
827 }
828 
829 NS_IMETHODIMP
GetMsgInputStream(nsIMsgDBHdr * aMsgHdr,bool * aReusable,nsIInputStream ** aInputStream)830 nsMsgDBFolder::GetMsgInputStream(nsIMsgDBHdr* aMsgHdr, bool* aReusable,
831                                  nsIInputStream** aInputStream) {
832   NS_ENSURE_ARG_POINTER(aMsgHdr);
833   NS_ENSURE_ARG_POINTER(aReusable);
834   NS_ENSURE_ARG_POINTER(aInputStream);
835   nsCOMPtr<nsIMsgPluggableStore> msgStore;
836   nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
837   NS_ENSURE_SUCCESS(rv, rv);
838   nsCString storeToken;
839   rv = aMsgHdr->GetStringProperty("storeToken", getter_Copies(storeToken));
840   NS_ENSURE_SUCCESS(rv, rv);
841   int64_t offset;
842   rv = msgStore->GetMsgInputStream(this, storeToken, &offset, aMsgHdr,
843                                    aReusable, aInputStream);
844   NS_ENSURE_SUCCESS(rv, rv);
845   nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(*aInputStream));
846   if (seekableStream) rv = seekableStream->Seek(PR_SEEK_SET, offset);
847   NS_WARNING_ASSERTION(seekableStream || !offset,
848                        "non-zero offset w/ non-seekable stream");
849   return rv;
850 }
851 
852 // path coming in is the root path without the leaf name,
853 // on the way out, it's the whole path.
CreateFileForDB(const nsAString & userLeafName,nsIFile * path,nsIFile ** dbFile)854 nsresult nsMsgDBFolder::CreateFileForDB(const nsAString& userLeafName,
855                                         nsIFile* path, nsIFile** dbFile) {
856   NS_ENSURE_ARG_POINTER(dbFile);
857 
858   nsAutoString proposedDBName(userLeafName);
859   NS_MsgHashIfNecessary(proposedDBName);
860 
861   // (note, the caller of this will be using the dbFile to call db->Open()
862   // will turn the path into summary file path, and append the ".msf" extension)
863   //
864   // we want db->Open() to create a new summary file
865   // so we have to jump through some hoops to make sure the .msf it will
866   // create is unique.  now that we've got the "safe" proposedDBName,
867   // we append ".msf" to see if the file exists.  if so, we make the name
868   // unique and then string off the ".msf" so that we pass the right thing
869   // into Open().  this isn't ideal, since this is not atomic
870   // but it will make do.
871   nsresult rv;
872   nsCOMPtr<nsIFile> dbPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
873   NS_ENSURE_SUCCESS(rv, rv);
874   dbPath->InitWithFile(path);
875   proposedDBName.AppendLiteral(SUMMARY_SUFFIX);
876   dbPath->Append(proposedDBName);
877   bool exists;
878   dbPath->Exists(&exists);
879   if (exists) {
880     rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
881     NS_ENSURE_SUCCESS(rv, rv);
882     dbPath->GetLeafName(proposedDBName);
883   }
884   // now, take the ".msf" off
885   proposedDBName.SetLength(proposedDBName.Length() - SUMMARY_SUFFIX_LENGTH);
886   dbPath->SetLeafName(proposedDBName);
887 
888   dbPath.forget(dbFile);
889   return NS_OK;
890 }
891 
892 NS_IMETHODIMP
GetMsgDatabase(nsIMsgDatabase ** aMsgDatabase)893 nsMsgDBFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase) {
894   NS_ENSURE_ARG_POINTER(aMsgDatabase);
895   GetDatabase();
896   if (!mDatabase) return NS_ERROR_FAILURE;
897   NS_ADDREF(*aMsgDatabase = mDatabase);
898   mDatabase->SetLastUseTime(PR_Now());
899   return NS_OK;
900 }
901 
902 NS_IMETHODIMP
SetMsgDatabase(nsIMsgDatabase * aMsgDatabase)903 nsMsgDBFolder::SetMsgDatabase(nsIMsgDatabase* aMsgDatabase) {
904   if (mDatabase) {
905     // commit here - db might go away when all these refs are released.
906     mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
907     mDatabase->RemoveListener(this);
908     mDatabase->ClearCachedHdrs();
909     if (!aMsgDatabase) {
910       mDatabase->GetNewList(m_newMsgs);
911     }
912   }
913   mDatabase = aMsgDatabase;
914 
915   if (aMsgDatabase) aMsgDatabase->AddListener(this);
916   return NS_OK;
917 }
918 
919 NS_IMETHODIMP
GetDatabaseOpen(bool * aOpen)920 nsMsgDBFolder::GetDatabaseOpen(bool* aOpen) {
921   NS_ENSURE_ARG_POINTER(aOpen);
922 
923   *aOpen = (mDatabase != nullptr);
924   return NS_OK;
925 }
926 
927 NS_IMETHODIMP
GetBackupMsgDatabase(nsIMsgDatabase ** aMsgDatabase)928 nsMsgDBFolder::GetBackupMsgDatabase(nsIMsgDatabase** aMsgDatabase) {
929   NS_ENSURE_ARG_POINTER(aMsgDatabase);
930   nsresult rv = OpenBackupMsgDatabase();
931   NS_ENSURE_SUCCESS(rv, rv);
932   if (!mBackupDatabase) return NS_ERROR_FAILURE;
933 
934   NS_ADDREF(*aMsgDatabase = mBackupDatabase);
935   return NS_OK;
936 }
937 
938 NS_IMETHODIMP
GetDBFolderInfoAndDB(nsIDBFolderInfo ** folderInfo,nsIMsgDatabase ** database)939 nsMsgDBFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo,
940                                     nsIMsgDatabase** database) {
941   return NS_ERROR_NOT_IMPLEMENTED;
942 }
943 
944 NS_IMETHODIMP
OnReadChanged(nsIDBChangeListener * aInstigator)945 nsMsgDBFolder::OnReadChanged(nsIDBChangeListener* aInstigator) {
946   /* do nothing.  if you care about this, override it.  see nsNewsFolder.cpp */
947   return NS_OK;
948 }
949 
950 NS_IMETHODIMP
OnJunkScoreChanged(nsIDBChangeListener * aInstigator)951 nsMsgDBFolder::OnJunkScoreChanged(nsIDBChangeListener* aInstigator) {
952   NotifyFolderEvent(kJunkStatusChanged);
953   return NS_OK;
954 }
955 
956 NS_IMETHODIMP
OnHdrPropertyChanged(nsIMsgDBHdr * aHdrToChange,bool aPreChange,uint32_t * aStatus,nsIDBChangeListener * aInstigator)957 nsMsgDBFolder::OnHdrPropertyChanged(nsIMsgDBHdr* aHdrToChange, bool aPreChange,
958                                     uint32_t* aStatus,
959                                     nsIDBChangeListener* aInstigator) {
960   /* do nothing.  if you care about this, override it.*/
961   return NS_OK;
962 }
963 
964 // 1.  When the status of a message changes.
OnHdrFlagsChanged(nsIMsgDBHdr * aHdrChanged,uint32_t aOldFlags,uint32_t aNewFlags,nsIDBChangeListener * aInstigator)965 NS_IMETHODIMP nsMsgDBFolder::OnHdrFlagsChanged(
966     nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags,
967     nsIDBChangeListener* aInstigator) {
968   if (aHdrChanged) {
969     SendFlagNotifications(aHdrChanged, aOldFlags, aNewFlags);
970     UpdateSummaryTotals(true);
971   }
972 
973   // The old state was new message state
974   // We check and see if this state has changed
975   if (aOldFlags & nsMsgMessageFlags::New) {
976     // state changing from new to something else
977     if (!(aNewFlags & nsMsgMessageFlags::New))
978       CheckWithNewMessagesStatus(false);
979   }
980 
981   return NS_OK;
982 }
983 
CheckWithNewMessagesStatus(bool messageAdded)984 nsresult nsMsgDBFolder::CheckWithNewMessagesStatus(bool messageAdded) {
985   bool hasNewMessages;
986   if (messageAdded)
987     SetHasNewMessages(true);
988   else  // message modified or deleted
989   {
990     if (mDatabase) {
991       nsresult rv = mDatabase->HasNew(&hasNewMessages);
992       NS_ENSURE_SUCCESS(rv, rv);
993       SetHasNewMessages(hasNewMessages);
994     }
995   }
996 
997   return NS_OK;
998 }
999 
1000 // 3.  When a message gets deleted, we need to see if it was new
1001 //     When we lose a new message we need to check if there are still new
1002 //     messages
OnHdrDeleted(nsIMsgDBHdr * aHdrChanged,nsMsgKey aParentKey,int32_t aFlags,nsIDBChangeListener * aInstigator)1003 NS_IMETHODIMP nsMsgDBFolder::OnHdrDeleted(nsIMsgDBHdr* aHdrChanged,
1004                                           nsMsgKey aParentKey, int32_t aFlags,
1005                                           nsIDBChangeListener* aInstigator) {
1006   // check to see if a new message is being deleted
1007   // as in this case, if there is only one new message and it's being deleted
1008   // the folder newness has to be cleared.
1009   CheckWithNewMessagesStatus(false);
1010   // Remove all processing flags.  This is generally a good thing although
1011   // undo-ing a message back into position will not re-gain the flags.
1012   nsMsgKey msgKey;
1013   aHdrChanged->GetMessageKey(&msgKey);
1014   AndProcessingFlags(msgKey, 0);
1015   return OnHdrAddedOrDeleted(aHdrChanged, false);
1016 }
1017 
1018 // 2.  When a new messages gets added, we need to see if it's new.
OnHdrAdded(nsIMsgDBHdr * aHdrChanged,nsMsgKey aParentKey,int32_t aFlags,nsIDBChangeListener * aInstigator)1019 NS_IMETHODIMP nsMsgDBFolder::OnHdrAdded(nsIMsgDBHdr* aHdrChanged,
1020                                         nsMsgKey aParentKey, int32_t aFlags,
1021                                         nsIDBChangeListener* aInstigator) {
1022   if (aFlags & nsMsgMessageFlags::New) CheckWithNewMessagesStatus(true);
1023   return OnHdrAddedOrDeleted(aHdrChanged, true);
1024 }
1025 
OnHdrAddedOrDeleted(nsIMsgDBHdr * aHdrChanged,bool added)1026 nsresult nsMsgDBFolder::OnHdrAddedOrDeleted(nsIMsgDBHdr* aHdrChanged,
1027                                             bool added) {
1028   if (added)
1029     NotifyItemAdded(aHdrChanged);
1030   else
1031     NotifyItemRemoved(aHdrChanged);
1032   UpdateSummaryTotals(true);
1033   return NS_OK;
1034 }
1035 
OnParentChanged(nsMsgKey aKeyChanged,nsMsgKey oldParent,nsMsgKey newParent,nsIDBChangeListener * aInstigator)1036 NS_IMETHODIMP nsMsgDBFolder::OnParentChanged(nsMsgKey aKeyChanged,
1037                                              nsMsgKey oldParent,
1038                                              nsMsgKey newParent,
1039                                              nsIDBChangeListener* aInstigator) {
1040   nsCOMPtr<nsIMsgDBHdr> hdrChanged;
1041   mDatabase->GetMsgHdrForKey(aKeyChanged, getter_AddRefs(hdrChanged));
1042   // In reality we probably want to just change the parent because otherwise we
1043   // will lose things like selection.
1044   if (hdrChanged) {
1045     // First delete the child from the old threadParent
1046     OnHdrAddedOrDeleted(hdrChanged, false);
1047     // Then add it to the new threadParent
1048     OnHdrAddedOrDeleted(hdrChanged, true);
1049   }
1050   return NS_OK;
1051 }
1052 
OnAnnouncerGoingAway(nsIDBChangeAnnouncer * instigator)1053 NS_IMETHODIMP nsMsgDBFolder::OnAnnouncerGoingAway(
1054     nsIDBChangeAnnouncer* instigator) {
1055   if (mBackupDatabase && instigator == mBackupDatabase) {
1056     mBackupDatabase->RemoveListener(this);
1057     mBackupDatabase = nullptr;
1058   } else if (mDatabase) {
1059     mDatabase->RemoveListener(this);
1060     mDatabase = nullptr;
1061   }
1062   return NS_OK;
1063 }
1064 
OnEvent(nsIMsgDatabase * aDB,const char * aEvent)1065 NS_IMETHODIMP nsMsgDBFolder::OnEvent(nsIMsgDatabase* aDB, const char* aEvent) {
1066   return NS_OK;
1067 }
1068 
GetManyHeadersToDownload(bool * retval)1069 NS_IMETHODIMP nsMsgDBFolder::GetManyHeadersToDownload(bool* retval) {
1070   NS_ENSURE_ARG_POINTER(retval);
1071   int32_t numTotalMessages;
1072 
1073   // is there any reason to return false?
1074   if (!mDatabase)
1075     *retval = true;
1076   else if (NS_SUCCEEDED(GetTotalMessages(false, &numTotalMessages)) &&
1077            numTotalMessages <= 0)
1078     *retval = true;
1079   else
1080     *retval = false;
1081   return NS_OK;
1082 }
1083 
MsgFitsDownloadCriteria(nsMsgKey msgKey,bool * result)1084 nsresult nsMsgDBFolder::MsgFitsDownloadCriteria(nsMsgKey msgKey, bool* result) {
1085   if (!mDatabase) return NS_ERROR_FAILURE;
1086 
1087   nsresult rv;
1088   nsCOMPtr<nsIMsgDBHdr> hdr;
1089   rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
1090   if (NS_FAILED(rv)) return rv;
1091 
1092   if (hdr) {
1093     uint32_t msgFlags = 0;
1094     hdr->GetFlags(&msgFlags);
1095     // check if we already have this message body offline
1096     if (!(msgFlags & nsMsgMessageFlags::Offline)) {
1097       *result = true;
1098       // check against the server download size limit .
1099       nsCOMPtr<nsIMsgIncomingServer> incomingServer;
1100       rv = GetServer(getter_AddRefs(incomingServer));
1101       if (NS_SUCCEEDED(rv) && incomingServer) {
1102         bool limitDownloadSize = false;
1103         rv = incomingServer->GetLimitOfflineMessageSize(&limitDownloadSize);
1104         NS_ENSURE_SUCCESS(rv, rv);
1105         if (limitDownloadSize) {
1106           int32_t maxDownloadMsgSize = 0;
1107           uint32_t msgSize;
1108           hdr->GetMessageSize(&msgSize);
1109           rv = incomingServer->GetMaxMessageSize(&maxDownloadMsgSize);
1110           NS_ENSURE_SUCCESS(rv, rv);
1111           maxDownloadMsgSize *= 1024;
1112           if (msgSize > (uint32_t)maxDownloadMsgSize) *result = false;
1113         }
1114       }
1115     }
1116   }
1117   return NS_OK;
1118 }
1119 
GetSupportsOffline(bool * aSupportsOffline)1120 NS_IMETHODIMP nsMsgDBFolder::GetSupportsOffline(bool* aSupportsOffline) {
1121   NS_ENSURE_ARG_POINTER(aSupportsOffline);
1122   if (mFlags & nsMsgFolderFlags::Virtual) {
1123     *aSupportsOffline = false;
1124     return NS_OK;
1125   }
1126 
1127   nsCOMPtr<nsIMsgIncomingServer> server;
1128   nsresult rv = GetServer(getter_AddRefs(server));
1129   NS_ENSURE_SUCCESS(rv, rv);
1130   if (!server) return NS_ERROR_FAILURE;
1131 
1132   int32_t offlineSupportLevel;
1133   rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
1134   NS_ENSURE_SUCCESS(rv, rv);
1135 
1136   *aSupportsOffline = (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR);
1137   return NS_OK;
1138 }
1139 
ShouldStoreMsgOffline(nsMsgKey msgKey,bool * result)1140 NS_IMETHODIMP nsMsgDBFolder::ShouldStoreMsgOffline(nsMsgKey msgKey,
1141                                                    bool* result) {
1142   NS_ENSURE_ARG(result);
1143   uint32_t flags = 0;
1144   *result = false;
1145   GetFlags(&flags);
1146   return flags & nsMsgFolderFlags::Offline
1147              ? MsgFitsDownloadCriteria(msgKey, result)
1148              : NS_OK;
1149 }
1150 
HasMsgOffline(nsMsgKey msgKey,bool * result)1151 NS_IMETHODIMP nsMsgDBFolder::HasMsgOffline(nsMsgKey msgKey, bool* result) {
1152   NS_ENSURE_ARG(result);
1153   *result = false;
1154   GetDatabase();
1155   if (!mDatabase) return NS_ERROR_FAILURE;
1156 
1157   nsresult rv;
1158   nsCOMPtr<nsIMsgDBHdr> hdr;
1159   rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
1160   if (NS_FAILED(rv)) return rv;
1161 
1162   if (hdr) {
1163     uint32_t msgFlags = 0;
1164     hdr->GetFlags(&msgFlags);
1165     // check if we already have this message body offline
1166     if ((msgFlags & nsMsgMessageFlags::Offline)) *result = true;
1167   }
1168   return NS_OK;
1169 }
1170 
GetOfflineMsgFolder(nsMsgKey msgKey,nsIMsgFolder ** aMsgFolder)1171 NS_IMETHODIMP nsMsgDBFolder::GetOfflineMsgFolder(nsMsgKey msgKey,
1172                                                  nsIMsgFolder** aMsgFolder) {
1173   NS_ENSURE_ARG_POINTER(aMsgFolder);
1174   nsCOMPtr<nsIMsgFolder> subMsgFolder;
1175   GetDatabase();
1176   if (!mDatabase) return NS_ERROR_FAILURE;
1177 
1178   nsCOMPtr<nsIMsgDBHdr> hdr;
1179   nsresult rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
1180   if (NS_FAILED(rv)) return rv;
1181 
1182   if (hdr) {
1183     uint32_t msgFlags = 0;
1184     hdr->GetFlags(&msgFlags);
1185     // Check if we already have this message body offline
1186     if ((msgFlags & nsMsgMessageFlags::Offline)) {
1187       NS_IF_ADDREF(*aMsgFolder = this);
1188       return NS_OK;
1189     }
1190   }
1191   // it's okay to not get a folder. Folder is remain unchanged in that case.
1192   return NS_OK;
1193 }
1194 
GetFlags(uint32_t * _retval)1195 NS_IMETHODIMP nsMsgDBFolder::GetFlags(uint32_t* _retval) {
1196   ReadDBFolderInfo(false);
1197   *_retval = mFlags;
1198   return NS_OK;
1199 }
1200 
ReadFromFolderCacheElem(nsIMsgFolderCacheElement * element)1201 NS_IMETHODIMP nsMsgDBFolder::ReadFromFolderCacheElem(
1202     nsIMsgFolderCacheElement* element) {
1203   nsresult rv = NS_OK;
1204 
1205   element->GetInt32Property("flags", (int32_t*)&mFlags);
1206   element->GetInt32Property("totalMsgs", &mNumTotalMessages);
1207   element->GetInt32Property("totalUnreadMsgs", &mNumUnreadMessages);
1208   element->GetInt32Property("pendingUnreadMsgs", &mNumPendingUnreadMessages);
1209   element->GetInt32Property("pendingMsgs", &mNumPendingTotalMessages);
1210   element->GetInt64Property("expungedBytes", &mExpungedBytes);
1211   element->GetInt64Property("folderSize", &mFolderSize);
1212 
1213 #ifdef DEBUG_bienvenu1
1214   nsCString uri;
1215   GetURI(uri);
1216   printf("read total %ld for %s\n", mNumTotalMessages, uri.get());
1217 #endif
1218   return rv;
1219 }
1220 
GetFolderCacheKey(nsIFile ** aFile,bool createDBIfMissing)1221 nsresult nsMsgDBFolder::GetFolderCacheKey(
1222     nsIFile** aFile, bool createDBIfMissing /* = false */) {
1223   nsresult rv;
1224   nsCOMPtr<nsIFile> path;
1225   rv = GetFilePath(getter_AddRefs(path));
1226 
1227   // now we put a new file  in aFile, because we're going to change it.
1228   nsCOMPtr<nsIFile> dbPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1229   NS_ENSURE_SUCCESS(rv, rv);
1230 
1231   if (dbPath) {
1232     dbPath->InitWithFile(path);
1233     // if not a server, we need to convert to a db Path with .msf on the end
1234     bool isServer = false;
1235     GetIsServer(&isServer);
1236 
1237     // if it's a server, we don't need the .msf appended to the name
1238     if (!isServer) {
1239       nsCOMPtr<nsIFile> summaryName;
1240       rv = GetSummaryFileLocation(dbPath, getter_AddRefs(summaryName));
1241       dbPath->InitWithFile(summaryName);
1242 
1243       // create the .msf file
1244       // see bug #244217 for details
1245       bool exists;
1246       if (createDBIfMissing && NS_SUCCEEDED(dbPath->Exists(&exists)) &&
1247           !exists) {
1248         rv = dbPath->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
1249         NS_ENSURE_SUCCESS(rv, rv);
1250       }
1251     }
1252   }
1253   dbPath.forget(aFile);
1254   return rv;
1255 }
1256 
FlushToFolderCache()1257 nsresult nsMsgDBFolder::FlushToFolderCache() {
1258   nsresult rv;
1259   nsCOMPtr<nsIMsgAccountManager> accountManager =
1260       do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
1261   if (NS_SUCCEEDED(rv) && accountManager) {
1262     nsCOMPtr<nsIMsgFolderCache> folderCache;
1263     rv = accountManager->GetFolderCache(getter_AddRefs(folderCache));
1264     if (NS_SUCCEEDED(rv) && folderCache)
1265       rv = WriteToFolderCache(folderCache, false);
1266   }
1267   return rv;
1268 }
1269 
WriteToFolderCache(nsIMsgFolderCache * folderCache,bool deep)1270 NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCache(nsIMsgFolderCache* folderCache,
1271                                                 bool deep) {
1272   nsresult rv = NS_OK;
1273 
1274   if (folderCache) {
1275     nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
1276     nsCOMPtr<nsIFile> dbPath;
1277     rv = GetFolderCacheKey(getter_AddRefs(dbPath));
1278 #ifdef DEBUG_bienvenu1
1279     bool exists;
1280     NS_ASSERTION(NS_SUCCEEDED(dbPath->Exists(&exists)) && exists,
1281                  "file spec we're adding to cache should exist");
1282 #endif
1283     if (NS_SUCCEEDED(rv) && dbPath) {
1284       nsCString persistentPath;
1285       rv = dbPath->GetPersistentDescriptor(persistentPath);
1286       NS_ENSURE_SUCCESS(rv, rv);
1287       rv = folderCache->GetCacheElement(persistentPath, true,
1288                                         getter_AddRefs(cacheElement));
1289       if (NS_SUCCEEDED(rv) && cacheElement)
1290         rv = WriteToFolderCacheElem(cacheElement);
1291     }
1292 
1293     if (deep) {
1294       for (nsIMsgFolder* msgFolder : mSubFolders) {
1295         rv = msgFolder->WriteToFolderCache(folderCache, true);
1296         if (NS_FAILED(rv)) break;
1297       }
1298     }
1299   }
1300   return rv;
1301 }
1302 
WriteToFolderCacheElem(nsIMsgFolderCacheElement * element)1303 NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCacheElem(
1304     nsIMsgFolderCacheElement* element) {
1305   nsresult rv = NS_OK;
1306 
1307   element->SetInt32Property("flags", (int32_t)mFlags);
1308   element->SetInt32Property("totalMsgs", mNumTotalMessages);
1309   element->SetInt32Property("totalUnreadMsgs", mNumUnreadMessages);
1310   element->SetInt32Property("pendingUnreadMsgs", mNumPendingUnreadMessages);
1311   element->SetInt32Property("pendingMsgs", mNumPendingTotalMessages);
1312   element->SetInt64Property("expungedBytes", mExpungedBytes);
1313   element->SetInt64Property("folderSize", mFolderSize);
1314 
1315 #ifdef DEBUG_bienvenu1
1316   nsCString uri;
1317   GetURI(uri);
1318   printf("writing total %ld for %s\n", mNumTotalMessages, uri.get());
1319 #endif
1320   return rv;
1321 }
1322 
1323 NS_IMETHODIMP
AddMessageDispositionState(nsIMsgDBHdr * aMessage,nsMsgDispositionState aDispositionFlag)1324 nsMsgDBFolder::AddMessageDispositionState(
1325     nsIMsgDBHdr* aMessage, nsMsgDispositionState aDispositionFlag) {
1326   NS_ENSURE_ARG_POINTER(aMessage);
1327 
1328   nsresult rv = GetDatabase();
1329   NS_ENSURE_SUCCESS(rv, NS_OK);
1330 
1331   nsMsgKey msgKey;
1332   aMessage->GetMessageKey(&msgKey);
1333 
1334   if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
1335     mDatabase->MarkReplied(msgKey, true, nullptr);
1336   else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
1337     mDatabase->MarkForwarded(msgKey, true, nullptr);
1338   else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Redirected)
1339     mDatabase->MarkRedirected(msgKey, true, nullptr);
1340   return NS_OK;
1341 }
1342 
AddMarkAllReadUndoAction(nsIMsgWindow * msgWindow,nsMsgKey * thoseMarked,uint32_t numMarked)1343 nsresult nsMsgDBFolder::AddMarkAllReadUndoAction(nsIMsgWindow* msgWindow,
1344                                                  nsMsgKey* thoseMarked,
1345                                                  uint32_t numMarked) {
1346   RefPtr<nsMsgReadStateTxn> readStateTxn = new nsMsgReadStateTxn();
1347   if (!readStateTxn) return NS_ERROR_OUT_OF_MEMORY;
1348 
1349   nsresult rv = readStateTxn->Init(this, numMarked, thoseMarked);
1350   NS_ENSURE_SUCCESS(rv, rv);
1351 
1352   rv = readStateTxn->SetTransactionType(nsIMessenger::eMarkAllMsg);
1353   NS_ENSURE_SUCCESS(rv, rv);
1354 
1355   nsCOMPtr<nsITransactionManager> txnMgr;
1356   rv = msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
1357   NS_ENSURE_SUCCESS(rv, rv);
1358 
1359   rv = txnMgr->DoTransaction(readStateTxn);
1360   NS_ENSURE_SUCCESS(rv, rv);
1361   return rv;
1362 }
1363 
1364 NS_IMETHODIMP
MarkAllMessagesRead(nsIMsgWindow * aMsgWindow)1365 nsMsgDBFolder::MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) {
1366   nsresult rv = GetDatabase();
1367   m_newMsgs.Clear();
1368 
1369   if (NS_SUCCEEDED(rv)) {
1370     EnableNotifications(allMessageCountNotifications, false);
1371     nsTArray<nsMsgKey> thoseMarked;
1372     rv = mDatabase->MarkAllRead(thoseMarked);
1373     EnableNotifications(allMessageCountNotifications, true);
1374     NS_ENSURE_SUCCESS(rv, rv);
1375 
1376     // Setup a undo-state
1377     if (aMsgWindow && thoseMarked.Length() > 0)
1378       rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked.Elements(),
1379                                     thoseMarked.Length());
1380   }
1381 
1382   SetHasNewMessages(false);
1383   return rv;
1384 }
1385 
MarkThreadRead(nsIMsgThread * thread)1386 NS_IMETHODIMP nsMsgDBFolder::MarkThreadRead(nsIMsgThread* thread) {
1387   nsresult rv = GetDatabase();
1388   NS_ENSURE_SUCCESS(rv, rv);
1389   nsTArray<nsMsgKey> keys;
1390   return mDatabase->MarkThreadRead(thread, nullptr, keys);
1391 }
1392 
1393 NS_IMETHODIMP
OnStartRunningUrl(nsIURI * aUrl)1394 nsMsgDBFolder::OnStartRunningUrl(nsIURI* aUrl) { return NS_OK; }
1395 
1396 NS_IMETHODIMP
OnStopRunningUrl(nsIURI * aUrl,nsresult aExitCode)1397 nsMsgDBFolder::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
1398   NS_ENSURE_ARG_POINTER(aUrl);
1399   nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
1400   if (mailUrl) {
1401     bool updatingFolder = false;
1402     if (NS_SUCCEEDED(mailUrl->GetUpdatingFolder(&updatingFolder)) &&
1403         updatingFolder)
1404       NotifyFolderEvent(kFolderLoaded);
1405 
1406     // be sure to remove ourselves as a url listener
1407     mailUrl->UnRegisterListener(this);
1408   }
1409   return NS_OK;
1410 }
1411 
1412 NS_IMETHODIMP
GetRetentionSettings(nsIMsgRetentionSettings ** settings)1413 nsMsgDBFolder::GetRetentionSettings(nsIMsgRetentionSettings** settings) {
1414   NS_ENSURE_ARG_POINTER(settings);
1415   *settings = nullptr;
1416   nsresult rv = NS_OK;
1417   bool useServerDefaults = false;
1418   if (!m_retentionSettings) {
1419     nsCString useServerRetention;
1420     GetStringProperty(kUseServerRetentionProp, useServerRetention);
1421     if (useServerRetention.EqualsLiteral("1")) {
1422       nsCOMPtr<nsIMsgIncomingServer> incomingServer;
1423       rv = GetServer(getter_AddRefs(incomingServer));
1424       if (NS_SUCCEEDED(rv) && incomingServer) {
1425         rv = incomingServer->GetRetentionSettings(settings);
1426         useServerDefaults = true;
1427       }
1428     } else {
1429       GetDatabase();
1430       if (mDatabase) {
1431         // get the settings from the db - if the settings from the db say the
1432         // folder is not overriding the incoming server settings, get the
1433         // settings from the server.
1434         rv = mDatabase->GetMsgRetentionSettings(settings);
1435         if (NS_SUCCEEDED(rv) && *settings) {
1436           (*settings)->GetUseServerDefaults(&useServerDefaults);
1437           if (useServerDefaults) {
1438             nsCOMPtr<nsIMsgIncomingServer> incomingServer;
1439             rv = GetServer(getter_AddRefs(incomingServer));
1440             NS_IF_RELEASE(*settings);
1441             if (NS_SUCCEEDED(rv) && incomingServer)
1442               incomingServer->GetRetentionSettings(settings);
1443           }
1444           if (useServerRetention.EqualsLiteral("1") != useServerDefaults) {
1445             if (useServerDefaults)
1446               useServerRetention.Assign('1');
1447             else
1448               useServerRetention.Assign('0');
1449             SetStringProperty(kUseServerRetentionProp, useServerRetention);
1450           }
1451         }
1452       } else
1453         return NS_ERROR_FAILURE;
1454     }
1455     // Only cache the retention settings if we've overridden the server
1456     // settings (otherwise, we won't notice changes to the server settings).
1457     if (!useServerDefaults) m_retentionSettings = *settings;
1458   } else
1459     NS_IF_ADDREF(*settings = m_retentionSettings);
1460 
1461   return rv;
1462 }
1463 
SetRetentionSettings(nsIMsgRetentionSettings * settings)1464 NS_IMETHODIMP nsMsgDBFolder::SetRetentionSettings(
1465     nsIMsgRetentionSettings* settings) {
1466   bool useServerDefaults;
1467   nsCString useServerRetention;
1468 
1469   settings->GetUseServerDefaults(&useServerDefaults);
1470   if (useServerDefaults) {
1471     useServerRetention.Assign('1');
1472     m_retentionSettings = nullptr;
1473   } else {
1474     useServerRetention.Assign('0');
1475     m_retentionSettings = settings;
1476   }
1477   SetStringProperty(kUseServerRetentionProp, useServerRetention);
1478   GetDatabase();
1479   if (mDatabase) mDatabase->SetMsgRetentionSettings(settings);
1480   return NS_OK;
1481 }
1482 
GetDownloadSettings(nsIMsgDownloadSettings ** settings)1483 NS_IMETHODIMP nsMsgDBFolder::GetDownloadSettings(
1484     nsIMsgDownloadSettings** settings) {
1485   NS_ENSURE_ARG_POINTER(settings);
1486   nsresult rv = NS_OK;
1487   if (!m_downloadSettings) {
1488     GetDatabase();
1489     if (mDatabase) {
1490       // get the settings from the db - if the settings from the db say the
1491       // folder is not overriding the incoming server settings, get the settings
1492       // from the server.
1493       rv =
1494           mDatabase->GetMsgDownloadSettings(getter_AddRefs(m_downloadSettings));
1495       if (NS_SUCCEEDED(rv) && m_downloadSettings) {
1496         bool useServerDefaults;
1497         m_downloadSettings->GetUseServerDefaults(&useServerDefaults);
1498         if (useServerDefaults) {
1499           nsCOMPtr<nsIMsgIncomingServer> incomingServer;
1500           rv = GetServer(getter_AddRefs(incomingServer));
1501           if (NS_SUCCEEDED(rv) && incomingServer)
1502             incomingServer->GetDownloadSettings(
1503                 getter_AddRefs(m_downloadSettings));
1504         }
1505       }
1506     }
1507   }
1508   NS_IF_ADDREF(*settings = m_downloadSettings);
1509   return rv;
1510 }
1511 
SetDownloadSettings(nsIMsgDownloadSettings * settings)1512 NS_IMETHODIMP nsMsgDBFolder::SetDownloadSettings(
1513     nsIMsgDownloadSettings* settings) {
1514   m_downloadSettings = settings;
1515   return NS_OK;
1516 }
1517 
IsCommandEnabled(const nsACString & command,bool * result)1518 NS_IMETHODIMP nsMsgDBFolder::IsCommandEnabled(const nsACString& command,
1519                                               bool* result) {
1520   NS_ENSURE_ARG_POINTER(result);
1521   *result = true;
1522   return NS_OK;
1523 }
1524 
WriteStartOfNewLocalMessage()1525 nsresult nsMsgDBFolder::WriteStartOfNewLocalMessage() {
1526   nsAutoCString result;
1527   uint32_t writeCount;
1528   time_t now = time((time_t*)0);
1529   char* ct = ctime(&now);
1530   ct[24] = 0;
1531   result = "From - ";
1532   result += ct;
1533   result += MSG_LINEBREAK;
1534   m_bytesAddedToLocalMsg = result.Length();
1535 
1536   nsCOMPtr<nsISeekableStream> seekable;
1537 
1538   if (m_offlineHeader) seekable = do_QueryInterface(m_tempMessageStream);
1539 
1540   m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
1541 
1542   constexpr auto MozillaStatus = "X-Mozilla-Status: 0001"_ns MSG_LINEBREAK;
1543   m_tempMessageStream->Write(MozillaStatus.get(), MozillaStatus.Length(),
1544                              &writeCount);
1545   m_bytesAddedToLocalMsg += writeCount;
1546   constexpr auto MozillaStatus2 =
1547       "X-Mozilla-Status2: 00000000"_ns MSG_LINEBREAK;
1548   m_bytesAddedToLocalMsg += MozillaStatus2.Length();
1549   return m_tempMessageStream->Write(MozillaStatus2.get(),
1550                                     MozillaStatus2.Length(), &writeCount);
1551 }
1552 
StartNewOfflineMessage()1553 nsresult nsMsgDBFolder::StartNewOfflineMessage() {
1554   bool isLocked;
1555   GetLocked(&isLocked);
1556   bool hasSemaphore = false;
1557   if (isLocked) {
1558     // it's OK if we, the folder, have the semaphore.
1559     TestSemaphore(static_cast<nsIMsgFolder*>(this), &hasSemaphore);
1560     if (!hasSemaphore) {
1561       NS_WARNING("folder locked trying to download offline");
1562       return NS_MSG_FOLDER_BUSY;
1563     }
1564   }
1565   nsresult rv = GetOfflineStoreOutputStream(
1566       m_offlineHeader, getter_AddRefs(m_tempMessageStream));
1567   if (NS_SUCCEEDED(rv) && !hasSemaphore)
1568     AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
1569   if (NS_SUCCEEDED(rv)) WriteStartOfNewLocalMessage();
1570   m_numOfflineMsgLines = 0;
1571   return rv;
1572 }
1573 
EndNewOfflineMessage()1574 nsresult nsMsgDBFolder::EndNewOfflineMessage() {
1575   nsCOMPtr<nsISeekableStream> seekable;
1576   int64_t curStorePos;
1577   uint64_t messageOffset;
1578   uint32_t messageSize;
1579 
1580   nsMsgKey messageKey;
1581 
1582   nsresult rv = GetDatabase();
1583   NS_ENSURE_SUCCESS(rv, rv);
1584 
1585   m_offlineHeader->GetMessageKey(&messageKey);
1586   if (m_tempMessageStream) seekable = do_QueryInterface(m_tempMessageStream);
1587 
1588   nsCOMPtr<nsIMsgPluggableStore> msgStore;
1589   GetMsgStore(getter_AddRefs(msgStore));
1590 
1591   if (seekable) {
1592     mDatabase->MarkOffline(messageKey, true, nullptr);
1593     m_tempMessageStream->Flush();
1594     int64_t tellPos;
1595     seekable->Tell(&tellPos);
1596     curStorePos = tellPos;
1597 
1598     // N.B. This only works if we've set the offline flag for the message,
1599     // so be careful about moving the call to MarkOffline above.
1600     m_offlineHeader->GetMessageOffset(&messageOffset);
1601     curStorePos -= messageOffset;
1602     m_offlineHeader->SetOfflineMessageSize(curStorePos);
1603     m_offlineHeader->GetMessageSize(&messageSize);
1604     messageSize += m_bytesAddedToLocalMsg;
1605     // unix/mac has a one byte line ending, but the imap server returns
1606     // crlf terminated lines.
1607     if (MSG_LINEBREAK_LEN == 1) messageSize -= m_numOfflineMsgLines;
1608 
1609     // We clear the offline flag on the message if the size
1610     // looks wrong. Check if we're off by more than one byte per line.
1611     if (messageSize > (uint32_t)curStorePos &&
1612         (messageSize - (uint32_t)curStorePos) >
1613             (uint32_t)m_numOfflineMsgLines) {
1614       mDatabase->MarkOffline(messageKey, false, nullptr);
1615       // we should truncate the offline store at messageOffset
1616       ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
1617       if (msgStore)
1618         // this closes the stream
1619         msgStore->DiscardNewMessage(m_tempMessageStream, m_offlineHeader);
1620       else
1621         m_tempMessageStream->Close();
1622       m_tempMessageStream = nullptr;
1623 #ifdef _DEBUG
1624       nsAutoCString message("Offline message too small: messageSize=");
1625       message.AppendInt(messageSize);
1626       message.AppendLiteral(" curStorePos=");
1627       message.AppendInt(curStorePos);
1628       message.AppendLiteral(" numOfflineMsgLines=");
1629       message.AppendInt(m_numOfflineMsgLines);
1630       message.AppendLiteral(" bytesAdded=");
1631       message.AppendInt(m_bytesAddedToLocalMsg);
1632       NS_ERROR(message.get());
1633 #endif
1634       m_offlineHeader = nullptr;
1635       return NS_ERROR_FAILURE;
1636     } else
1637       m_offlineHeader->SetLineCount(m_numOfflineMsgLines);
1638   }
1639   if (msgStore)
1640     msgStore->FinishNewMessage(m_tempMessageStream, m_offlineHeader);
1641 
1642   m_offlineHeader = nullptr;
1643   if (m_tempMessageStream) {
1644     m_tempMessageStream->Close();
1645     m_tempMessageStream = nullptr;
1646   }
1647   return NS_OK;
1648 }
1649 
CompactOfflineStore(nsIMsgWindow * inWindow,nsIUrlListener * aListener)1650 nsresult nsMsgDBFolder::CompactOfflineStore(nsIMsgWindow* inWindow,
1651                                             nsIUrlListener* aListener) {
1652   nsresult rv;
1653   nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
1654       do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv);
1655   NS_ENSURE_SUCCESS(rv, rv);
1656   return folderCompactor->Compact(this, true, aListener, inWindow);
1657 }
1658 
1659 class AutoCompactEvent : public mozilla::Runnable {
1660  public:
AutoCompactEvent(nsIMsgWindow * aMsgWindow,nsMsgDBFolder * aFolder)1661   AutoCompactEvent(nsIMsgWindow* aMsgWindow, nsMsgDBFolder* aFolder)
1662       : mozilla::Runnable("AutoCompactEvent"),
1663         mMsgWindow(aMsgWindow),
1664         mFolder(aFolder) {}
1665 
Run()1666   NS_IMETHOD Run() {
1667     if (mFolder) mFolder->HandleAutoCompactEvent(mMsgWindow);
1668     return NS_OK;
1669   }
1670 
1671  private:
1672   nsCOMPtr<nsIMsgWindow> mMsgWindow;
1673   RefPtr<nsMsgDBFolder> mFolder;
1674 };
1675 
HandleAutoCompactEvent(nsIMsgWindow * aWindow)1676 nsresult nsMsgDBFolder::HandleAutoCompactEvent(nsIMsgWindow* aWindow) {
1677   nsresult rv;
1678   nsCOMPtr<nsIMsgAccountManager> accountMgr =
1679       do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
1680   if (NS_SUCCEEDED(rv)) {
1681     nsTArray<RefPtr<nsIMsgIncomingServer>> allServers;
1682     rv = accountMgr->GetAllServers(allServers);
1683     NS_ENSURE_SUCCESS(rv, rv);
1684     uint32_t numServers = allServers.Length();
1685     if (numServers > 0) {
1686       nsTArray<RefPtr<nsIMsgFolder>> folderArray;
1687       nsTArray<RefPtr<nsIMsgFolder>> offlineFolderArray;
1688       int64_t totalExpungedBytes = 0;
1689       int64_t offlineExpungedBytes = 0;
1690       int64_t localExpungedBytes = 0;
1691       uint32_t serverIndex = 0;
1692       do {
1693         nsCOMPtr<nsIMsgIncomingServer> server(allServers[serverIndex]);
1694         nsCOMPtr<nsIMsgPluggableStore> msgStore;
1695         rv = server->GetMsgStore(getter_AddRefs(msgStore));
1696         NS_ENSURE_SUCCESS(rv, rv);
1697         if (!msgStore) continue;
1698         bool supportsCompaction;
1699         msgStore->GetSupportsCompaction(&supportsCompaction);
1700         if (!supportsCompaction) continue;
1701         nsCOMPtr<nsIMsgFolder> rootFolder;
1702         rv = server->GetRootFolder(getter_AddRefs(rootFolder));
1703         if (NS_SUCCEEDED(rv) && rootFolder) {
1704           int32_t offlineSupportLevel;
1705           rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
1706           NS_ENSURE_SUCCESS(rv, rv);
1707           nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
1708           rootFolder->GetDescendants(allDescendants);
1709           int64_t expungedBytes = 0;
1710           if (offlineSupportLevel > 0) {
1711             uint32_t flags;
1712             for (auto folder : allDescendants) {
1713               expungedBytes = 0;
1714               folder->GetFlags(&flags);
1715               if (flags & nsMsgFolderFlags::Offline)
1716                 folder->GetExpungedBytes(&expungedBytes);
1717               if (expungedBytes > 0) {
1718                 offlineFolderArray.AppendElement(folder);
1719                 offlineExpungedBytes += expungedBytes;
1720               }
1721             }
1722           } else  // pop or local
1723           {
1724             for (auto folder : allDescendants) {
1725               expungedBytes = 0;
1726               folder->GetExpungedBytes(&expungedBytes);
1727               if (expungedBytes > 0) {
1728                 folderArray.AppendElement(folder);
1729                 localExpungedBytes += expungedBytes;
1730               }
1731             }
1732           }
1733         }
1734       } while (++serverIndex < numServers);
1735       totalExpungedBytes = localExpungedBytes + offlineExpungedBytes;
1736       int32_t purgeThreshold;
1737       rv = GetPurgeThreshold(&purgeThreshold);
1738       NS_ENSURE_SUCCESS(rv, rv);
1739       if (totalExpungedBytes > ((int64_t)purgeThreshold * 1024)) {
1740         bool okToCompact = false;
1741         nsCOMPtr<nsIPrefService> pref =
1742             do_GetService(NS_PREFSERVICE_CONTRACTID);
1743         nsCOMPtr<nsIPrefBranch> branch;
1744         pref->GetBranch("", getter_AddRefs(branch));
1745 
1746         bool askBeforePurge;
1747         branch->GetBoolPref(PREF_MAIL_PURGE_ASK, &askBeforePurge);
1748         if (askBeforePurge && aWindow) {
1749           nsCOMPtr<nsIStringBundle> bundle;
1750           rv = GetBaseStringBundle(getter_AddRefs(bundle));
1751           NS_ENSURE_SUCCESS(rv, rv);
1752 
1753           nsAutoString compactSize;
1754           FormatFileSize(totalExpungedBytes, true, compactSize);
1755 
1756           bool neverAsk = false;  // "Do not ask..." - unchecked by default.
1757           int32_t buttonPressed = 0;
1758 
1759           nsCOMPtr<nsIWindowWatcher> ww(
1760               do_GetService(NS_WINDOWWATCHER_CONTRACTID));
1761           nsCOMPtr<nsIWritablePropertyBag2> props(
1762               do_CreateInstance("@mozilla.org/hash-property-bag;1"));
1763           props->SetPropertyAsAString(u"compactSize"_ns, compactSize);
1764           nsCOMPtr<mozIDOMWindowProxy> migrateWizard;
1765           rv = ww->OpenWindow(
1766               nullptr,
1767               "chrome://messenger/content/compactFoldersDialog.xhtml"_ns,
1768               "_blank"_ns, "chrome,dialog,modal,centerscreen"_ns, props,
1769               getter_AddRefs(migrateWizard));
1770           NS_ENSURE_SUCCESS(rv, rv);
1771 
1772           rv = props->GetPropertyAsBool(u"checked"_ns, &neverAsk);
1773           NS_ENSURE_SUCCESS(rv, rv);
1774 
1775           rv =
1776               props->GetPropertyAsInt32(u"buttonNumClicked"_ns, &buttonPressed);
1777           NS_ENSURE_SUCCESS(rv, rv);
1778 
1779           if (buttonPressed == 0) {
1780             okToCompact = true;
1781             if (neverAsk)  // [X] Remove deletions automatically and do not ask
1782               branch->SetBoolPref(PREF_MAIL_PURGE_ASK, false);
1783           }
1784         } else
1785           okToCompact = aWindow || !askBeforePurge;
1786 
1787         if (okToCompact) {
1788           NotifyFolderEvent(kAboutToCompact);
1789 
1790           if (localExpungedBytes > 0) {
1791             nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
1792                 do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
1793             NS_ENSURE_SUCCESS(rv, rv);
1794 
1795             if (offlineExpungedBytes > 0)
1796               folderCompactor->CompactFolders(folderArray, offlineFolderArray,
1797                                               nullptr, aWindow);
1798             else
1799               folderCompactor->CompactFolders(folderArray, {}, nullptr,
1800                                               aWindow);
1801           } else if (offlineExpungedBytes > 0)
1802             CompactAllOfflineStores(nullptr, aWindow, offlineFolderArray);
1803         }
1804       }
1805     }
1806   }
1807   return rv;
1808 }
1809 
AutoCompact(nsIMsgWindow * aWindow)1810 nsresult nsMsgDBFolder::AutoCompact(nsIMsgWindow* aWindow) {
1811   // we don't check for null aWindow, because this routine can get called
1812   // in unit tests where we have no window. Just assume not OK if no window.
1813   bool prompt;
1814   nsresult rv = GetPromptPurgeThreshold(&prompt);
1815   NS_ENSURE_SUCCESS(rv, rv);
1816   PRTime timeNow = PR_Now();  // time in microseconds
1817   PRTime timeAfterOneHourOfLastPurgeCheck = gtimeOfLastPurgeCheck + oneHour;
1818   if (timeAfterOneHourOfLastPurgeCheck < timeNow && prompt) {
1819     gtimeOfLastPurgeCheck = timeNow;
1820     nsCOMPtr<nsIRunnable> event = new AutoCompactEvent(aWindow, this);
1821     // Post this as an event because it can put up an alert, which
1822     // might cause issues depending on the stack when we are called.
1823     if (event) NS_DispatchToCurrentThread(event);
1824   }
1825   return rv;
1826 }
1827 
1828 NS_IMETHODIMP
CompactAllOfflineStores(nsIUrlListener * aUrlListener,nsIMsgWindow * aWindow,const nsTArray<RefPtr<nsIMsgFolder>> & aOfflineFolderArray)1829 nsMsgDBFolder::CompactAllOfflineStores(
1830     nsIUrlListener* aUrlListener, nsIMsgWindow* aWindow,
1831     const nsTArray<RefPtr<nsIMsgFolder>>& aOfflineFolderArray) {
1832   nsresult rv;
1833   nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
1834       do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv);
1835   NS_ENSURE_SUCCESS(rv, rv);
1836   return folderCompactor->CompactFolders({}, aOfflineFolderArray, aUrlListener,
1837                                          aWindow);
1838 }
1839 
GetPromptPurgeThreshold(bool * aPrompt)1840 nsresult nsMsgDBFolder::GetPromptPurgeThreshold(bool* aPrompt) {
1841   NS_ENSURE_ARG(aPrompt);
1842   nsresult rv;
1843   nsCOMPtr<nsIPrefBranch> prefBranch =
1844       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1845   if (NS_SUCCEEDED(rv) && prefBranch) {
1846     rv = prefBranch->GetBoolPref(PREF_MAIL_PROMPT_PURGE_THRESHOLD, aPrompt);
1847     if (NS_FAILED(rv)) {
1848       *aPrompt = false;
1849       rv = NS_OK;
1850     }
1851   }
1852   return rv;
1853 }
1854 
GetPurgeThreshold(int32_t * aThreshold)1855 nsresult nsMsgDBFolder::GetPurgeThreshold(int32_t* aThreshold) {
1856   NS_ENSURE_ARG(aThreshold);
1857   nsresult rv;
1858   nsCOMPtr<nsIPrefBranch> prefBranch =
1859       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1860   if (NS_SUCCEEDED(rv) && prefBranch) {
1861     int32_t thresholdMB = 200;
1862     bool thresholdMigrated = false;
1863     prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, &thresholdMB);
1864     prefBranch->GetBoolPref(PREF_MAIL_PURGE_MIGRATED, &thresholdMigrated);
1865     if (!thresholdMigrated) {
1866       *aThreshold = 20480;
1867       (void)prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD, aThreshold);
1868       if (*aThreshold / 1024 != thresholdMB) {
1869         thresholdMB = std::max(1, *aThreshold / 1024);
1870         prefBranch->SetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, thresholdMB);
1871       }
1872       prefBranch->SetBoolPref(PREF_MAIL_PURGE_MIGRATED, true);
1873     }
1874     *aThreshold = thresholdMB * 1024;
1875   }
1876   return rv;
1877 }
1878 
1879 NS_IMETHODIMP  // called on the folder that is renamed or about to be deleted
MatchOrChangeFilterDestination(nsIMsgFolder * newFolder,bool caseInsensitive,bool * found)1880 nsMsgDBFolder::MatchOrChangeFilterDestination(nsIMsgFolder* newFolder,
1881                                               bool caseInsensitive,
1882                                               bool* found) {
1883   NS_ENSURE_ARG_POINTER(found);
1884   *found = false;
1885   nsCString oldUri;
1886   nsresult rv = GetURI(oldUri);
1887   NS_ENSURE_SUCCESS(rv, rv);
1888 
1889   nsCString newUri;
1890   if (newFolder)  // for matching uri's this will be null
1891   {
1892     rv = newFolder->GetURI(newUri);
1893     NS_ENSURE_SUCCESS(rv, rv);
1894   }
1895 
1896   nsCOMPtr<nsIMsgFilterList> filterList;
1897   nsCOMPtr<nsIMsgAccountManager> accountMgr =
1898       do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
1899   NS_ENSURE_SUCCESS(rv, rv);
1900 
1901   nsTArray<RefPtr<nsIMsgIncomingServer>> allServers;
1902   rv = accountMgr->GetAllServers(allServers);
1903   NS_ENSURE_SUCCESS(rv, rv);
1904 
1905   for (auto server : allServers) {
1906     if (server) {
1907       bool canHaveFilters;
1908       rv = server->GetCanHaveFilters(&canHaveFilters);
1909       if (NS_SUCCEEDED(rv) && canHaveFilters) {
1910         // update the filterlist to match the new folder name
1911         rv = server->GetFilterList(nullptr, getter_AddRefs(filterList));
1912         if (NS_SUCCEEDED(rv) && filterList) {
1913           bool match;
1914           rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri,
1915                                                      caseInsensitive, &match);
1916           if (NS_SUCCEEDED(rv) && match) {
1917             *found = true;
1918             if (newFolder && !newUri.IsEmpty())
1919               rv = filterList->SaveToDefaultFile();
1920           }
1921         }
1922         // update the editable filterlist to match the new folder name
1923         rv = server->GetEditableFilterList(nullptr, getter_AddRefs(filterList));
1924         if (NS_SUCCEEDED(rv) && filterList) {
1925           bool match;
1926           rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri,
1927                                                      caseInsensitive, &match);
1928           if (NS_SUCCEEDED(rv) && match) {
1929             *found = true;
1930             if (newFolder && !newUri.IsEmpty())
1931               rv = filterList->SaveToDefaultFile();
1932           }
1933         }
1934       }
1935     }
1936   }
1937   return rv;
1938 }
1939 
1940 NS_IMETHODIMP
GetDBTransferInfo(nsIDBFolderInfo ** aTransferInfo)1941 nsMsgDBFolder::GetDBTransferInfo(nsIDBFolderInfo** aTransferInfo) {
1942   nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
1943   nsCOMPtr<nsIMsgDatabase> db;
1944   GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db));
1945   if (dbFolderInfo) dbFolderInfo->GetTransferInfo(aTransferInfo);
1946   return NS_OK;
1947 }
1948 
1949 NS_IMETHODIMP
SetDBTransferInfo(nsIDBFolderInfo * aTransferInfo)1950 nsMsgDBFolder::SetDBTransferInfo(nsIDBFolderInfo* aTransferInfo) {
1951   NS_ENSURE_ARG(aTransferInfo);
1952   nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
1953   nsCOMPtr<nsIMsgDatabase> db;
1954   GetMsgDatabase(getter_AddRefs(db));
1955   if (db) {
1956     db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
1957     if (dbFolderInfo) {
1958       dbFolderInfo->InitFromTransferInfo(aTransferInfo);
1959       dbFolderInfo->SetBooleanProperty("forceReparse", false);
1960     }
1961     db->SetSummaryValid(true);
1962   }
1963   return NS_OK;
1964 }
1965 
1966 NS_IMETHODIMP
GetStringProperty(const char * propertyName,nsACString & propertyValue)1967 nsMsgDBFolder::GetStringProperty(const char* propertyName,
1968                                  nsACString& propertyValue) {
1969   NS_ENSURE_ARG_POINTER(propertyName);
1970   nsCOMPtr<nsIFile> dbPath;
1971   nsresult rv = GetFolderCacheKey(getter_AddRefs(dbPath));
1972   if (dbPath) {
1973     nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
1974     rv = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
1975     if (cacheElement)  // try to get from cache
1976       rv = cacheElement->GetStringProperty(propertyName, propertyValue);
1977     if (NS_FAILED(rv))  // if failed, then try to get from db
1978     {
1979       nsCOMPtr<nsIDBFolderInfo> folderInfo;
1980       nsCOMPtr<nsIMsgDatabase> db;
1981       bool exists;
1982       rv = dbPath->Exists(&exists);
1983       if (NS_FAILED(rv) || !exists) return NS_MSG_ERROR_FOLDER_MISSING;
1984       rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
1985       if (NS_SUCCEEDED(rv))
1986         rv = folderInfo->GetCharProperty(propertyName, propertyValue);
1987     }
1988   }
1989   return rv;
1990 }
1991 
1992 NS_IMETHODIMP
SetStringProperty(const char * propertyName,const nsACString & propertyValue)1993 nsMsgDBFolder::SetStringProperty(const char* propertyName,
1994                                  const nsACString& propertyValue) {
1995   NS_ENSURE_ARG_POINTER(propertyName);
1996   nsCOMPtr<nsIFile> dbPath;
1997   GetFolderCacheKey(getter_AddRefs(dbPath));
1998   if (dbPath) {
1999     nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
2000     GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
2001     if (cacheElement)  // try to set in the cache
2002       cacheElement->SetStringProperty(propertyName, propertyValue);
2003   }
2004   nsCOMPtr<nsIDBFolderInfo> folderInfo;
2005   nsCOMPtr<nsIMsgDatabase> db;
2006   nsresult rv =
2007       GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
2008   if (NS_SUCCEEDED(rv)) {
2009     folderInfo->SetCharProperty(propertyName, propertyValue);
2010     db->Commit(nsMsgDBCommitType::kLargeCommit);  // committing the db also
2011                                                   // commits the cache
2012   }
2013   return NS_OK;
2014 }
2015 
2016 // Get/Set ForcePropertyEmpty is only used with inherited properties
2017 NS_IMETHODIMP
GetForcePropertyEmpty(const char * aPropertyName,bool * _retval)2018 nsMsgDBFolder::GetForcePropertyEmpty(const char* aPropertyName, bool* _retval) {
2019   NS_ENSURE_ARG_POINTER(_retval);
2020   nsAutoCString nameEmpty(aPropertyName);
2021   nameEmpty.AppendLiteral(".empty");
2022   nsCString value;
2023   GetStringProperty(nameEmpty.get(), value);
2024   *_retval = value.EqualsLiteral("true");
2025   return NS_OK;
2026 }
2027 
2028 NS_IMETHODIMP
SetForcePropertyEmpty(const char * aPropertyName,bool aValue)2029 nsMsgDBFolder::SetForcePropertyEmpty(const char* aPropertyName, bool aValue) {
2030   nsAutoCString nameEmpty(aPropertyName);
2031   nameEmpty.AppendLiteral(".empty");
2032   return SetStringProperty(nameEmpty.get(), aValue ? "true"_ns : ""_ns);
2033 }
2034 
2035 NS_IMETHODIMP
GetInheritedStringProperty(const char * aPropertyName,nsACString & aPropertyValue)2036 nsMsgDBFolder::GetInheritedStringProperty(const char* aPropertyName,
2037                                           nsACString& aPropertyValue) {
2038   NS_ENSURE_ARG_POINTER(aPropertyName);
2039   nsCString value;
2040   nsCOMPtr<nsIMsgIncomingServer> server;
2041 
2042   bool forceEmpty = false;
2043 
2044   if (!mIsServer) {
2045     GetForcePropertyEmpty(aPropertyName, &forceEmpty);
2046   } else {
2047     // root folders must get their values from the server
2048     GetServer(getter_AddRefs(server));
2049     if (server) server->GetForcePropertyEmpty(aPropertyName, &forceEmpty);
2050   }
2051 
2052   if (forceEmpty) {
2053     aPropertyValue.Truncate();
2054     return NS_OK;
2055   }
2056 
2057   // servers will automatically inherit from the preference
2058   // mail.server.default.(propertyName)
2059   if (server) return server->GetCharValue(aPropertyName, aPropertyValue);
2060 
2061   GetStringProperty(aPropertyName, value);
2062   if (value.IsEmpty()) {
2063     // inherit from the parent
2064     nsCOMPtr<nsIMsgFolder> parent;
2065     GetParent(getter_AddRefs(parent));
2066     if (parent)
2067       return parent->GetInheritedStringProperty(aPropertyName, aPropertyValue);
2068   }
2069 
2070   aPropertyValue.Assign(value);
2071   return NS_OK;
2072 }
2073 
2074 NS_IMETHODIMP
OnMessageClassified(const nsACString & aMsgURI,nsMsgJunkStatus aClassification,uint32_t aJunkPercent)2075 nsMsgDBFolder::OnMessageClassified(const nsACString& aMsgURI,
2076                                    nsMsgJunkStatus aClassification,
2077                                    uint32_t aJunkPercent) {
2078   nsresult rv = GetDatabase();
2079   NS_ENSURE_SUCCESS(rv, NS_OK);
2080 
2081   if (aMsgURI.IsEmpty())  // This signifies end of batch.
2082   {
2083     // Apply filters if needed.
2084     if (!mPostBayesMessagesToFilter.IsEmpty()) {
2085       // Apply post-bayes filtering.
2086       nsCOMPtr<nsIMsgFilterService> filterService(
2087           do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv));
2088       if (NS_SUCCEEDED(rv))
2089         // We use a null nsIMsgWindow because we don't want some sort of ui
2090         // appearing in the middle of automatic filtering (plus I really don't
2091         // want to propagate that value.)
2092         rv = filterService->ApplyFilters(nsMsgFilterType::PostPlugin,
2093                                          mPostBayesMessagesToFilter, this,
2094                                          nullptr, nullptr);
2095       mPostBayesMessagesToFilter.Clear();
2096     }
2097 
2098     // If we classified any messages, send out a notification.
2099     nsTArray<RefPtr<nsIMsgDBHdr>> hdrs;
2100     rv = MsgGetHeadersFromKeys(mDatabase, mClassifiedMsgKeys, hdrs);
2101     NS_ENSURE_SUCCESS(rv, rv);
2102     if (!hdrs.IsEmpty()) {
2103       nsCOMPtr<nsIMsgFolderNotificationService> notifier(
2104           do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv));
2105       NS_ENSURE_SUCCESS(rv, rv);
2106       notifier->NotifyMsgsClassified(hdrs, mBayesJunkClassifying,
2107                                      mBayesTraitClassifying);
2108     }
2109     return rv;
2110   }
2111 
2112   nsCOMPtr<nsIMsgIncomingServer> server;
2113   rv = GetServer(getter_AddRefs(server));
2114   NS_ENSURE_SUCCESS(rv, rv);
2115 
2116   nsCOMPtr<nsISpamSettings> spamSettings;
2117   rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
2118   NS_ENSURE_SUCCESS(rv, rv);
2119 
2120   nsCOMPtr<nsIMsgDBHdr> msgHdr;
2121   rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
2122   NS_ENSURE_SUCCESS(rv, rv);
2123 
2124   nsMsgKey msgKey;
2125   rv = msgHdr->GetMessageKey(&msgKey);
2126   NS_ENSURE_SUCCESS(rv, rv);
2127 
2128   // check if this message needs junk classification
2129   uint32_t processingFlags;
2130   GetProcessingFlags(msgKey, &processingFlags);
2131 
2132   if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) {
2133     mClassifiedMsgKeys.AppendElement(msgKey);
2134     AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyJunk);
2135 
2136     nsAutoCString msgJunkScore;
2137     msgJunkScore.AppendInt(aClassification == nsIJunkMailPlugin::JUNK
2138                                ? nsIJunkMailPlugin::IS_SPAM_SCORE
2139                                : nsIJunkMailPlugin::IS_HAM_SCORE);
2140     mDatabase->SetStringProperty(msgKey, "junkscore", msgJunkScore.get());
2141     mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "plugin");
2142 
2143     nsAutoCString strPercent;
2144     strPercent.AppendInt(aJunkPercent);
2145     mDatabase->SetStringProperty(msgKey, "junkpercent", strPercent.get());
2146 
2147     if (aClassification == nsIJunkMailPlugin::JUNK) {
2148       // IMAP has its own way of marking read.
2149       if (!(mFlags & nsMsgFolderFlags::ImapBox)) {
2150         bool markAsReadOnSpam;
2151         (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
2152         if (markAsReadOnSpam) {
2153           rv = mDatabase->MarkRead(msgKey, true, this);
2154           if (!NS_SUCCEEDED(rv))
2155             NS_WARNING("failed marking spam message as read");
2156         }
2157       }
2158       // mail folders will log junk hits with move info. Perhaps we should
2159       // add a log here for non-mail folders as well, that don't override
2160       // onMessageClassified
2161       // rv = spamSettings->LogJunkHit(msgHdr, false);
2162       // NS_ENSURE_SUCCESS(rv,rv);
2163     }
2164   }
2165   return NS_OK;
2166 }
2167 
2168 NS_IMETHODIMP
OnMessageTraitsClassified(const nsACString & aMsgURI,const nsTArray<uint32_t> & aTraits,const nsTArray<uint32_t> & aPercents)2169 nsMsgDBFolder::OnMessageTraitsClassified(const nsACString& aMsgURI,
2170                                          const nsTArray<uint32_t>& aTraits,
2171                                          const nsTArray<uint32_t>& aPercents) {
2172   if (aMsgURI.IsEmpty())  // This signifies end of batch
2173     return NS_OK;         // We are not handling batching
2174 
2175   MOZ_ASSERT(aTraits.Length() == aPercents.Length());
2176 
2177   nsresult rv;
2178   nsCOMPtr<nsIMsgDBHdr> msgHdr;
2179   rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
2180   NS_ENSURE_SUCCESS(rv, rv);
2181 
2182   nsMsgKey msgKey;
2183   rv = msgHdr->GetMessageKey(&msgKey);
2184   NS_ENSURE_SUCCESS(rv, rv);
2185 
2186   uint32_t processingFlags;
2187   GetProcessingFlags(msgKey, &processingFlags);
2188   if (!(processingFlags & nsMsgProcessingFlags::ClassifyTraits)) return NS_OK;
2189 
2190   AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyTraits);
2191 
2192   nsCOMPtr<nsIMsgTraitService> traitService;
2193   traitService = do_GetService("@mozilla.org/msg-trait-service;1", &rv);
2194   NS_ENSURE_SUCCESS(rv, rv);
2195 
2196   for (uint32_t i = 0; i < aTraits.Length(); i++) {
2197     if (aTraits[i] == nsIJunkMailPlugin::JUNK_TRAIT)
2198       continue;  // junk is processed by the junk listener
2199     nsAutoCString traitId;
2200     rv = traitService->GetId(aTraits[i], traitId);
2201     traitId.InsertLiteral("bayespercent/", 0);
2202     nsAutoCString strPercent;
2203     strPercent.AppendInt(aPercents[i]);
2204     mDatabase->SetStringPropertyByHdr(msgHdr, traitId.get(), strPercent.get());
2205   }
2206   return NS_OK;
2207 }
2208 
2209 /**
2210  * Call the filter plugins (XXX currently just one)
2211  */
2212 NS_IMETHODIMP
CallFilterPlugins(nsIMsgWindow * aMsgWindow,bool * aFiltersRun)2213 nsMsgDBFolder::CallFilterPlugins(nsIMsgWindow* aMsgWindow, bool* aFiltersRun) {
2214   NS_ENSURE_ARG_POINTER(aFiltersRun);
2215 
2216   nsString folderName;
2217   GetPrettyName(folderName);
2218   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2219           ("Running filter plugins on folder '%s'",
2220            NS_ConvertUTF16toUTF8(folderName).get()));
2221 
2222   *aFiltersRun = false;
2223   nsCOMPtr<nsIMsgIncomingServer> server;
2224   nsCOMPtr<nsISpamSettings> spamSettings;
2225   int32_t spamLevel = 0;
2226 
2227   nsresult rv = GetServer(getter_AddRefs(server));
2228   NS_ENSURE_SUCCESS(rv, rv);
2229 
2230   nsCString serverType;
2231   server->GetType(serverType);
2232 
2233   rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
2234   nsCOMPtr<nsIMsgFilterPlugin> filterPlugin;
2235   server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
2236   if (!filterPlugin)  // it's not an error not to have the filter plugin.
2237     return NS_OK;
2238   NS_ENSURE_SUCCESS(rv, rv);
2239 
2240   nsCOMPtr<nsIJunkMailPlugin> junkMailPlugin = do_QueryInterface(filterPlugin);
2241   if (!junkMailPlugin)  // we currently only support the junk mail plugin
2242     return NS_OK;
2243 
2244   // if it's a news folder, then we really don't support junk in the ui
2245   // yet the legacy spamLevel seems to think we should analyze it.
2246   // Maybe we should upgrade that, but for now let's not analyze. We'll
2247   // let an extension set an inherited property if they really want us to
2248   // analyze this. We need that anyway to allow extension-based overrides.
2249   // When we finalize adding junk in news to core, we'll deal with the
2250   // spamLevel issue
2251 
2252   // if this is the junk folder, or the trash folder
2253   // don't analyze for spam, because we don't care
2254   //
2255   // if it's the sent, unsent, templates, or drafts,
2256   // don't analyze for spam, because the user
2257   // created that message
2258   //
2259   // if it's a public imap folder, or another users
2260   // imap folder, don't analyze for spam, because
2261   // it's not ours to analyze
2262   //
2263 
2264   bool filterForJunk = true;
2265   if (serverType.EqualsLiteral("rss") ||
2266       (mFlags &
2267            (nsMsgFolderFlags::SpecialUse | nsMsgFolderFlags::ImapPublic |
2268             nsMsgFolderFlags::Newsgroup | nsMsgFolderFlags::ImapOtherUser) &&
2269        !(mFlags & nsMsgFolderFlags::Inbox)))
2270     filterForJunk = false;
2271 
2272   spamSettings->GetLevel(&spamLevel);
2273   if (!spamLevel) filterForJunk = false;
2274 
2275   /*
2276    * We'll use inherited folder properties for the junk trait to override the
2277    * standard server-based activation of junk processing. This provides a
2278    * hook for extensions to customize the application of junk filtering.
2279    * Set inherited property "dobayes.mailnews@mozilla.org#junk" to "true"
2280    * to force junk processing, and "false" to skip junk processing.
2281    */
2282 
2283   nsAutoCString junkEnableOverride;
2284   GetInheritedStringProperty("dobayes.mailnews@mozilla.org#junk",
2285                              junkEnableOverride);
2286   if (junkEnableOverride.EqualsLiteral("true"))
2287     filterForJunk = true;
2288   else if (junkEnableOverride.EqualsLiteral("false"))
2289     filterForJunk = false;
2290 
2291   bool userHasClassified = false;
2292   // if the user has not classified any messages yet, then we shouldn't bother
2293   // running the junk mail controls. This creates a better first use experience.
2294   // See Bug #250084.
2295   junkMailPlugin->GetUserHasClassified(&userHasClassified);
2296   if (!userHasClassified) filterForJunk = false;
2297 
2298   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2299           ("Will run Spam filter: %s", filterForJunk ? "true" : "false"));
2300 
2301   nsCOMPtr<nsIMsgDatabase> database(mDatabase);
2302   rv = GetMsgDatabase(getter_AddRefs(database));
2303   NS_ENSURE_SUCCESS(rv, rv);
2304 
2305   // check if trait processing needed
2306 
2307   nsCOMPtr<nsIMsgTraitService> traitService(
2308       do_GetService("@mozilla.org/msg-trait-service;1", &rv));
2309   NS_ENSURE_SUCCESS(rv, rv);
2310 
2311   nsTArray<uint32_t> proIndices;
2312   rv = traitService->GetEnabledProIndices(proIndices);
2313   bool filterForOther = false;
2314   // We just skip this on failure, since it is rarely used.
2315   if (NS_SUCCEEDED(rv)) {
2316     for (uint32_t i = 0; i < proIndices.Length(); ++i) {
2317       // The trait service determines which traits are globally enabled or
2318       // disabled. If a trait is enabled, it can still be made inactive
2319       // on a particular folder using an inherited property. To do that,
2320       // set "dobayes." + trait proID as an inherited folder property with
2321       // the string value "false"
2322       //
2323       // If any non-junk traits are active on the folder, then the bayes
2324       // processing will calculate probabilities for all enabled traits.
2325 
2326       if (proIndices[i] != nsIJunkMailPlugin::JUNK_TRAIT) {
2327         filterForOther = true;
2328         nsAutoCString traitId;
2329         nsAutoCString property("dobayes.");
2330         traitService->GetId(proIndices[i], traitId);
2331         property.Append(traitId);
2332         nsAutoCString isEnabledOnFolder;
2333         GetInheritedStringProperty(property.get(), isEnabledOnFolder);
2334         if (isEnabledOnFolder.EqualsLiteral("false")) filterForOther = false;
2335         // We might have to allow a "true" override in the future, but
2336         // for now there is no way for that to affect the processing
2337         break;
2338       }
2339     }
2340   }
2341 
2342   // clang-format off
2343   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2344           ("Will run Trait classification: %s", filterForOther ? "true" : "false"));
2345   // clang-format on
2346 
2347   // Do we need to apply message filters?
2348   bool filterPostPlugin = false;  // Do we have a post-analysis filter?
2349   nsCOMPtr<nsIMsgFilterList> filterList;
2350   GetFilterList(aMsgWindow, getter_AddRefs(filterList));
2351   if (filterList) {
2352     uint32_t filterCount = 0;
2353     filterList->GetFilterCount(&filterCount);
2354     for (uint32_t index = 0; index < filterCount && !filterPostPlugin;
2355          ++index) {
2356       nsCOMPtr<nsIMsgFilter> filter;
2357       filterList->GetFilterAt(index, getter_AddRefs(filter));
2358       if (!filter) continue;
2359       nsMsgFilterTypeType filterType;
2360       filter->GetFilterType(&filterType);
2361       if (!(filterType & nsMsgFilterType::PostPlugin)) continue;
2362       bool enabled = false;
2363       filter->GetEnabled(&enabled);
2364       if (!enabled) continue;
2365       filterPostPlugin = true;
2366     }
2367   }
2368 
2369   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2370           ("Will run Post-classification filters: %s",
2371            filterPostPlugin ? "true" : "false"));
2372 
2373   // If there is nothing to do, leave now but let NotifyHdrsNotBeingClassified
2374   // generate the msgsClassified notification for all newly added messages as
2375   // tracked by the NotReportedClassified processing flag.
2376   if (!filterForOther && !filterForJunk && !filterPostPlugin) {
2377     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("No filters need to be run"));
2378     NotifyHdrsNotBeingClassified();
2379     return NS_OK;
2380   }
2381 
2382   // get the list of new messages
2383   //
2384   nsTArray<nsMsgKey> newKeys;
2385   rv = database->GetNewList(newKeys);
2386   NS_ENSURE_SUCCESS(rv, rv);
2387 
2388   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2389           ("Running filters on %" PRIu32 " new messages",
2390            (uint32_t)newKeys.Length()));
2391 
2392   nsTArray<nsMsgKey> newMessageKeys;
2393   // Start from m_saveNewMsgs (and clear its current state).  m_saveNewMsgs is
2394   // where we stash the list of new messages when we are told to clear the list
2395   // of new messages by the UI (which purges the list from the nsMsgDatabase).
2396   newMessageKeys.SwapElements(m_saveNewMsgs);
2397   newMessageKeys.AppendElements(newKeys);
2398 
2399   // build up list of keys to classify
2400   nsTArray<nsMsgKey> classifyMsgKeys;
2401   nsCString uri;
2402 
2403   uint32_t numNewMessages = newMessageKeys.Length();
2404   for (uint32_t i = 0; i < numNewMessages; ++i) {
2405     nsMsgKey msgKey = newMessageKeys[i];
2406     // clang-format off
2407     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2408             ("Running filters on message with key %" PRIu32, msgKeyToInt(msgKey)));
2409     // clang-format on
2410     nsCOMPtr<nsIMsgDBHdr> msgHdr;
2411     rv = database->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
2412     if (!NS_SUCCEEDED(rv)) continue;
2413     // per-message junk tests.
2414     bool filterMessageForJunk = false;
2415     while (filterForJunk)  // we'll break from this at the end
2416     {
2417       MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("Spam filter"));
2418       nsCString junkScore;
2419       msgHdr->GetStringProperty("junkscore", getter_Copies(junkScore));
2420       if (!junkScore.IsEmpty()) {
2421         // ignore already scored messages.
2422         MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2423                 ("Message already scored previously, skipping"));
2424         break;
2425       }
2426 
2427       bool whiteListMessage = false;
2428       spamSettings->CheckWhiteList(msgHdr, &whiteListMessage);
2429       if (whiteListMessage) {
2430         // mark this msg as non-junk, because we whitelisted it.
2431         nsAutoCString msgJunkScore;
2432         msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
2433         database->SetStringProperty(msgKey, "junkscore", msgJunkScore.get());
2434         database->SetStringProperty(msgKey, "junkscoreorigin", "whitelist");
2435         MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2436                 ("Message whitelisted, skipping"));
2437         break;  // skip this msg since it's in the white list
2438       }
2439       filterMessageForJunk = true;
2440 
2441       MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("Message is to be classified"));
2442       OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyJunk);
2443       // Since we are junk processing, we want to defer the msgsClassified
2444       // notification until the junk classification has occurred.  The event
2445       // is sufficiently reliable that we know this will be handled in
2446       // OnMessageClassified at the end of the batch.  We clear the
2447       // NotReportedClassified flag since we know the message is in good hands.
2448       AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::NotReportedClassified);
2449       break;
2450     }
2451 
2452     uint32_t processingFlags;
2453     GetProcessingFlags(msgKey, &processingFlags);
2454 
2455     bool filterMessageForOther = false;
2456     // trait processing
2457     if (!(processingFlags & nsMsgProcessingFlags::TraitsDone)) {
2458       // don't do trait processing on this message again
2459       OrProcessingFlags(msgKey, nsMsgProcessingFlags::TraitsDone);
2460       if (filterForOther) {
2461         filterMessageForOther = true;
2462         OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyTraits);
2463       }
2464     }
2465 
2466     if (filterMessageForJunk || filterMessageForOther)
2467       classifyMsgKeys.AppendElement(newMessageKeys[i]);
2468 
2469     // Set messages to filter post-bayes.
2470     // Have we already filtered this message?
2471     if (!(processingFlags & nsMsgProcessingFlags::FiltersDone)) {
2472       if (filterPostPlugin) {
2473         // Don't do filters on this message again.
2474         // (Only set this if we are actually filtering since this is
2475         // tantamount to a memory leak.)
2476         MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2477                 ("Filters done on this message"));
2478         OrProcessingFlags(msgKey, nsMsgProcessingFlags::FiltersDone);
2479         mPostBayesMessagesToFilter.AppendElement(msgHdr);
2480       }
2481     }
2482   }
2483 
2484   NotifyHdrsNotBeingClassified();
2485   // If there weren't any new messages, just return.
2486   if (newMessageKeys.IsEmpty()) return NS_OK;
2487 
2488   // If we do not need to do any work, leave.
2489   // (We needed to get the list of new messages so we could get their headers so
2490   // we can send notifications about them here.)
2491 
2492   if (!classifyMsgKeys.IsEmpty()) {
2493     // Remember what classifications are the source of this decision for when
2494     // we perform the notification in OnMessageClassified at the conclusion of
2495     // classification.
2496     mBayesJunkClassifying = filterForJunk;
2497     mBayesTraitClassifying = filterForOther;
2498 
2499     uint32_t numMessagesToClassify = classifyMsgKeys.Length();
2500     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2501             ("Running Spam classification on %" PRIu32 " messages",
2502              numMessagesToClassify));
2503 
2504     nsTArray<nsCString> messageURIs(numMessagesToClassify);
2505     for (uint32_t msgIndex = 0; msgIndex < numMessagesToClassify; ++msgIndex) {
2506       nsCString tmpStr;
2507       rv = GenerateMessageURI(classifyMsgKeys[msgIndex], tmpStr);
2508       if (NS_SUCCEEDED(rv)) {
2509         messageURIs.AppendElement(tmpStr);
2510       } else {
2511         NS_WARNING(
2512             "nsMsgDBFolder::CallFilterPlugins(): could not"
2513             " generate URI for message");
2514       }
2515     }
2516     // filterMsgs
2517     *aFiltersRun = true;
2518 
2519     // Already got proIndices, but need antiIndices too.
2520     nsTArray<uint32_t> antiIndices;
2521     rv = traitService->GetEnabledAntiIndices(antiIndices);
2522     NS_ENSURE_SUCCESS(rv, rv);
2523 
2524     rv = junkMailPlugin->ClassifyTraitsInMessages(
2525         messageURIs, proIndices, antiIndices, this, aMsgWindow, this);
2526   } else if (filterPostPlugin) {
2527     // Nothing to classify, so need to end batch ourselves. We do this so that
2528     // post analysis filters will run consistently on a folder, even if
2529     // disabled junk processing, which could be dynamic through whitelisting,
2530     // makes the bayes analysis unnecessary.
2531     OnMessageClassified(EmptyCString(), nsIJunkMailPlugin::UNCLASSIFIED, 0);
2532   }
2533 
2534   return rv;
2535 }
2536 
2537 /**
2538  * Adds the messages in the NotReportedClassified mProcessing set to the
2539  * (possibly empty) array of msgHdrsNotBeingClassified, and send the
2540  * nsIMsgFolderNotificationService notification.
2541  */
NotifyHdrsNotBeingClassified()2542 nsresult nsMsgDBFolder::NotifyHdrsNotBeingClassified() {
2543   if (mProcessingFlag[5].keys) {
2544     nsTArray<nsMsgKey> keys;
2545     mProcessingFlag[5].keys->ToMsgKeyArray(keys);
2546     if (keys.Length()) {
2547       nsresult rv = GetDatabase();
2548       NS_ENSURE_SUCCESS(rv, rv);
2549       nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrsNotBeingClassified;
2550       rv = MsgGetHeadersFromKeys(mDatabase, keys, msgHdrsNotBeingClassified);
2551       NS_ENSURE_SUCCESS(rv, rv);
2552 
2553       // Since we know we've handled all the NotReportedClassified messages,
2554       // we clear the set by deleting and recreating it.
2555       delete mProcessingFlag[5].keys;
2556       mProcessingFlag[5].keys = nsMsgKeySetU::Create();
2557       nsCOMPtr<nsIMsgFolderNotificationService> notifier(
2558           do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
2559       if (notifier)
2560         notifier->NotifyMsgsClassified(msgHdrsNotBeingClassified,
2561                                        // no classification is being performed
2562                                        false, false);
2563     }
2564   }
2565   return NS_OK;
2566 }
2567 
2568 NS_IMETHODIMP
GetLastMessageLoaded(nsMsgKey * aMsgKey)2569 nsMsgDBFolder::GetLastMessageLoaded(nsMsgKey* aMsgKey) {
2570   NS_ENSURE_ARG_POINTER(aMsgKey);
2571   *aMsgKey = mLastMessageLoaded;
2572   return NS_OK;
2573 }
2574 
2575 NS_IMETHODIMP
SetLastMessageLoaded(nsMsgKey aMsgKey)2576 nsMsgDBFolder::SetLastMessageLoaded(nsMsgKey aMsgKey) {
2577   mLastMessageLoaded = aMsgKey;
2578   return NS_OK;
2579 }
2580 
2581 // Returns true if: a) there is no need to prompt or b) the user is already
2582 // logged in or c) the user logged in successfully.
PromptForMasterPasswordIfNecessary()2583 bool nsMsgDBFolder::PromptForMasterPasswordIfNecessary() {
2584   nsresult rv;
2585   nsCOMPtr<nsIMsgAccountManager> accountManager =
2586       do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
2587   NS_ENSURE_SUCCESS(rv, false);
2588 
2589   bool userNeedsToAuthenticate = false;
2590   // if we're PasswordProtectLocalCache, then we need to find out if the server
2591   // is authenticated.
2592   (void)accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate);
2593   if (!userNeedsToAuthenticate) return true;
2594 
2595   // Do we have a master password?
2596   nsCOMPtr<nsIPK11TokenDB> tokenDB =
2597       do_GetService(NS_PK11TOKENDB_CONTRACTID, &rv);
2598   NS_ENSURE_SUCCESS(rv, false);
2599 
2600   nsCOMPtr<nsIPK11Token> token;
2601   rv = tokenDB->GetInternalKeyToken(getter_AddRefs(token));
2602   NS_ENSURE_SUCCESS(rv, false);
2603 
2604   bool result;
2605   rv = token->CheckPassword(EmptyCString(), &result);
2606   NS_ENSURE_SUCCESS(rv, false);
2607 
2608   if (result) {
2609     // We don't have a master password, so this function isn't supported,
2610     // therefore just tell account manager we've authenticated and return true.
2611     accountManager->SetUserNeedsToAuthenticate(false);
2612     return true;
2613   }
2614 
2615   // We have a master password, so try and login to the slot.
2616   rv = token->Login(false);
2617   if (NS_FAILED(rv))
2618     // Login failed, so we didn't get a password (e.g. prompt cancelled).
2619     return false;
2620 
2621   // Double-check that we are now logged in
2622   rv = token->IsLoggedIn(&result);
2623   NS_ENSURE_SUCCESS(rv, false);
2624 
2625   accountManager->SetUserNeedsToAuthenticate(!result);
2626   return result;
2627 }
2628 
2629 // this gets called after the last junk mail classification has run.
PerformBiffNotifications(void)2630 nsresult nsMsgDBFolder::PerformBiffNotifications(void) {
2631   nsCOMPtr<nsIMsgIncomingServer> server;
2632   nsresult rv = GetServer(getter_AddRefs(server));
2633   NS_ENSURE_SUCCESS(rv, rv);
2634   int32_t numBiffMsgs = 0;
2635   nsCOMPtr<nsIMsgFolder> root;
2636   rv = GetRootFolder(getter_AddRefs(root));
2637   root->GetNumNewMessages(true, &numBiffMsgs);
2638   if (numBiffMsgs > 0) {
2639     server->SetPerformingBiff(true);
2640     SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
2641     server->SetPerformingBiff(false);
2642   }
2643   return NS_OK;
2644 }
2645 
initializeStrings()2646 nsresult nsMsgDBFolder::initializeStrings() {
2647   nsresult rv;
2648   nsCOMPtr<nsIStringBundleService> bundleService =
2649       mozilla::services::GetStringBundleService();
2650   NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
2651   nsCOMPtr<nsIStringBundle> bundle;
2652   rv = bundleService->CreateBundle(
2653       "chrome://messenger/locale/messenger.properties", getter_AddRefs(bundle));
2654   NS_ENSURE_SUCCESS(rv, rv);
2655 
2656   bundle->GetStringFromName("inboxFolderName", kLocalizedInboxName);
2657   bundle->GetStringFromName("trashFolderName", kLocalizedTrashName);
2658   bundle->GetStringFromName("sentFolderName", kLocalizedSentName);
2659   bundle->GetStringFromName("draftsFolderName", kLocalizedDraftsName);
2660   bundle->GetStringFromName("templatesFolderName", kLocalizedTemplatesName);
2661   bundle->GetStringFromName("junkFolderName", kLocalizedJunkName);
2662   bundle->GetStringFromName("outboxFolderName", kLocalizedUnsentName);
2663   bundle->GetStringFromName("archivesFolderName", kLocalizedArchivesName);
2664 
2665   nsCOMPtr<nsIStringBundle> brandBundle;
2666   rv = bundleService->CreateBundle("chrome://branding/locale/brand.properties",
2667                                    getter_AddRefs(bundle));
2668   NS_ENSURE_SUCCESS(rv, rv);
2669   bundle->GetStringFromName("brandShortName", kLocalizedBrandShortName);
2670   return NS_OK;
2671 }
2672 
createCollationKeyGenerator()2673 nsresult nsMsgDBFolder::createCollationKeyGenerator() {
2674   nsresult rv = NS_OK;
2675 
2676   nsCOMPtr<nsICollationFactory> factory =
2677       do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
2678   NS_ENSURE_SUCCESS(rv, rv);
2679 
2680   return factory->CreateCollation(&gCollationKeyGenerator);
2681 }
2682 
2683 NS_IMETHODIMP
Init(const nsACString & uri)2684 nsMsgDBFolder::Init(const nsACString& uri) {
2685   mURI = uri;
2686   return CreateBaseMessageURI(uri);
2687 }
2688 
CreateBaseMessageURI(const nsACString & aURI)2689 nsresult nsMsgDBFolder::CreateBaseMessageURI(const nsACString& aURI) {
2690   // Each folder needs to implement this.
2691   return NS_OK;
2692 }
2693 
2694 NS_IMETHODIMP
GetURI(nsACString & name)2695 nsMsgDBFolder::GetURI(nsACString& name) {
2696   name = mURI;
2697   return NS_OK;
2698 }
2699 
2700 ////////////////////////////////////////////////////////////////////////////////
2701 #if 0
2702 typedef bool
2703 (*nsArrayFilter)(nsISupports* element, void* data);
2704 #endif
2705 ////////////////////////////////////////////////////////////////////////////////
2706 
2707 NS_IMETHODIMP
GetSubFolders(nsTArray<RefPtr<nsIMsgFolder>> & folders)2708 nsMsgDBFolder::GetSubFolders(nsTArray<RefPtr<nsIMsgFolder>>& folders) {
2709   folders.ClearAndRetainStorage();
2710   folders.SetCapacity(mSubFolders.Length());
2711   for (nsIMsgFolder* f : mSubFolders) {
2712     folders.AppendElement(f);
2713   }
2714   return NS_OK;
2715 }
2716 
2717 NS_IMETHODIMP
FindSubFolder(const nsACString & aEscapedSubFolderName,nsIMsgFolder ** aFolder)2718 nsMsgDBFolder::FindSubFolder(const nsACString& aEscapedSubFolderName,
2719                              nsIMsgFolder** aFolder) {
2720   // XXX use necko here
2721   nsAutoCString uri;
2722   uri.Append(mURI);
2723   uri.Append('/');
2724   uri.Append(aEscapedSubFolderName);
2725 
2726   return GetOrCreateFolder(uri, aFolder);
2727 }
2728 
2729 NS_IMETHODIMP
GetHasSubFolders(bool * _retval)2730 nsMsgDBFolder::GetHasSubFolders(bool* _retval) {
2731   NS_ENSURE_ARG_POINTER(_retval);
2732   *_retval = mSubFolders.Count() > 0;
2733   return NS_OK;
2734 }
2735 
2736 NS_IMETHODIMP
GetNumSubFolders(uint32_t * aResult)2737 nsMsgDBFolder::GetNumSubFolders(uint32_t* aResult) {
2738   NS_ENSURE_ARG_POINTER(aResult);
2739   *aResult = mSubFolders.Count();
2740   return NS_OK;
2741 }
2742 
AddFolderListener(nsIFolderListener * listener)2743 NS_IMETHODIMP nsMsgDBFolder::AddFolderListener(nsIFolderListener* listener) {
2744   NS_ENSURE_ARG_POINTER(listener);
2745   mListeners.AppendElement(listener);
2746   return NS_OK;
2747 }
2748 
RemoveFolderListener(nsIFolderListener * listener)2749 NS_IMETHODIMP nsMsgDBFolder::RemoveFolderListener(nsIFolderListener* listener) {
2750   NS_ENSURE_ARG_POINTER(listener);
2751   mListeners.RemoveElement(listener);
2752   return NS_OK;
2753 }
2754 
SetParent(nsIMsgFolder * aParent)2755 NS_IMETHODIMP nsMsgDBFolder::SetParent(nsIMsgFolder* aParent) {
2756   mParent = do_GetWeakReference(aParent);
2757   if (aParent) {
2758     nsresult rv;
2759     // servers do not have parents, so we must not be a server
2760     mIsServer = false;
2761     mIsServerIsValid = true;
2762 
2763     // also set the server itself while we're here.
2764     nsCOMPtr<nsIMsgIncomingServer> server;
2765     rv = aParent->GetServer(getter_AddRefs(server));
2766     if (NS_SUCCEEDED(rv) && server) mServer = do_GetWeakReference(server);
2767   }
2768   return NS_OK;
2769 }
2770 
GetParent(nsIMsgFolder ** aParent)2771 NS_IMETHODIMP nsMsgDBFolder::GetParent(nsIMsgFolder** aParent) {
2772   NS_ENSURE_ARG_POINTER(aParent);
2773   nsCOMPtr<nsIMsgFolder> parent = do_QueryReferent(mParent);
2774   parent.forget(aParent);
2775   return NS_OK;
2776 }
2777 
2778 NS_IMETHODIMP
GetMessages(nsIMsgEnumerator ** result)2779 nsMsgDBFolder::GetMessages(nsIMsgEnumerator** result) {
2780   NS_ENSURE_ARG_POINTER(result);
2781   // Make sure mDatabase is set.
2782   nsresult rv = GetDatabase();
2783   NS_ENSURE_SUCCESS(rv, rv);
2784   return mDatabase->EnumerateMessages(result);
2785 }
2786 
2787 NS_IMETHODIMP
UpdateFolder(nsIMsgWindow *)2788 nsMsgDBFolder::UpdateFolder(nsIMsgWindow*) { return NS_OK; }
2789 
2790 ////////////////////////////////////////////////////////////////////////////////
2791 
GetFolderURL(nsACString & url)2792 NS_IMETHODIMP nsMsgDBFolder::GetFolderURL(nsACString& url) {
2793   url.Assign(EmptyCString());
2794   return NS_OK;
2795 }
2796 
GetServer(nsIMsgIncomingServer ** aServer)2797 NS_IMETHODIMP nsMsgDBFolder::GetServer(nsIMsgIncomingServer** aServer) {
2798   NS_ENSURE_ARG_POINTER(aServer);
2799   nsresult rv;
2800   // short circuit the server if we have it.
2801   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
2802   if (NS_FAILED(rv)) {
2803     // try again after parsing the URI
2804     rv = parseURI(true);
2805     server = do_QueryReferent(mServer);
2806   }
2807   server.forget(aServer);
2808   return *aServer ? NS_OK : NS_ERROR_FAILURE;
2809 }
2810 
parseURI(bool needServer)2811 nsresult nsMsgDBFolder::parseURI(bool needServer) {
2812   nsresult rv;
2813   nsCOMPtr<nsIURL> url;
2814   rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
2815            .SetSpec(mURI)
2816            .Finalize(url);
2817   NS_ENSURE_SUCCESS(rv, rv);
2818 
2819   // empty path tells us it's a server.
2820   if (!mIsServerIsValid) {
2821     nsAutoCString path;
2822     rv = url->GetPathQueryRef(path);
2823     if (NS_SUCCEEDED(rv)) mIsServer = path.EqualsLiteral("/");
2824     mIsServerIsValid = true;
2825   }
2826 
2827   // grab the name off the leaf of the server
2828   if (mName.IsEmpty()) {
2829     // mName:
2830     // the name is the trailing directory in the path
2831     nsAutoCString fileName;
2832     nsAutoCString escapedFileName;
2833     url->GetFileName(escapedFileName);
2834     if (!escapedFileName.IsEmpty()) {
2835       // XXX conversion to unicode here? is fileName in UTF8?
2836       // yes, let's say it is in utf8
2837       MsgUnescapeString(escapedFileName, 0, fileName);
2838       NS_ASSERTION(mozilla::IsUtf8(fileName), "fileName is not in UTF-8");
2839       CopyUTF8toUTF16(fileName, mName);
2840     }
2841   }
2842 
2843   // grab the server by parsing the URI and looking it up
2844   // in the account manager...
2845   // But avoid this extra work by first asking the parent, if any
2846   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
2847   if (NS_FAILED(rv)) {
2848     // first try asking the parent instead of the URI
2849     nsCOMPtr<nsIMsgFolder> parentMsgFolder;
2850     GetParent(getter_AddRefs(parentMsgFolder));
2851 
2852     if (parentMsgFolder)
2853       rv = parentMsgFolder->GetServer(getter_AddRefs(server));
2854 
2855     // no parent. do the extra work of asking
2856     if (!server && needServer) {
2857       nsCOMPtr<nsIMsgAccountManager> accountManager =
2858           do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
2859       NS_ENSURE_SUCCESS(rv, rv);
2860 
2861       nsCString serverType;
2862       GetIncomingServerType(serverType);
2863       if (serverType.IsEmpty()) {
2864         NS_WARNING("can't determine folder's server type");
2865         return NS_ERROR_FAILURE;
2866       }
2867 
2868       rv = NS_MutateURI(url).SetScheme(serverType).Finalize(url);
2869       NS_ENSURE_SUCCESS(rv, rv);
2870       rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server));
2871       NS_ENSURE_SUCCESS(rv, rv);
2872     }
2873     mServer = do_GetWeakReference(server);
2874   } /* !mServer */
2875 
2876   // now try to find the local path for this folder
2877   if (server) {
2878     nsAutoCString newPath;
2879     nsAutoCString escapedUrlPath;
2880     nsAutoCString urlPath;
2881     url->GetFilePath(escapedUrlPath);
2882     if (!escapedUrlPath.IsEmpty()) {
2883       MsgUnescapeString(escapedUrlPath, 0, urlPath);
2884 
2885       // transform the filepath from the URI, such as
2886       // "/folder1/folder2/foldern"
2887       // to
2888       // "folder1.sbd/folder2.sbd/foldern"
2889       // (remove leading / and add .sbd to first n-1 folders)
2890       // to be appended onto the server's path
2891       bool isNewsFolder = false;
2892       nsAutoCString scheme;
2893       if (NS_SUCCEEDED(url->GetScheme(scheme))) {
2894         isNewsFolder = scheme.EqualsLiteral("news") ||
2895                        scheme.EqualsLiteral("snews") ||
2896                        scheme.EqualsLiteral("nntp");
2897       }
2898       NS_MsgCreatePathStringFromFolderURI(urlPath.get(), newPath, scheme,
2899                                           isNewsFolder);
2900     }
2901 
2902     // now append munged path onto server path
2903     nsCOMPtr<nsIFile> serverPath;
2904     rv = server->GetLocalPath(getter_AddRefs(serverPath));
2905     if (NS_FAILED(rv)) return rv;
2906 
2907     if (!mPath && serverPath) {
2908       if (!newPath.IsEmpty()) {
2909         // I hope this is temporary - Ultimately,
2910         // NS_MsgCreatePathStringFromFolderURI will need to be fixed.
2911 #if defined(XP_WIN)
2912         newPath.ReplaceChar('/', '\\');
2913 #endif
2914         rv = serverPath->AppendRelativeNativePath(newPath);
2915         NS_ASSERTION(NS_SUCCEEDED(rv), "failed to append to the serverPath");
2916         if (NS_FAILED(rv)) {
2917           mPath = nullptr;
2918           return rv;
2919         }
2920       }
2921       mPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
2922       NS_ENSURE_SUCCESS(rv, rv);
2923       mPath->InitWithFile(serverPath);
2924     }
2925     // URI is completely parsed when we've attempted to get the server
2926     mHaveParsedURI = true;
2927   }
2928   return NS_OK;
2929 }
2930 
2931 NS_IMETHODIMP
GetIsServer(bool * aResult)2932 nsMsgDBFolder::GetIsServer(bool* aResult) {
2933   NS_ENSURE_ARG_POINTER(aResult);
2934   // make sure we've parsed the URI
2935   if (!mIsServerIsValid) {
2936     nsresult rv = parseURI();
2937     if (NS_FAILED(rv) || !mIsServerIsValid) return NS_ERROR_FAILURE;
2938   }
2939 
2940   *aResult = mIsServer;
2941   return NS_OK;
2942 }
2943 
2944 NS_IMETHODIMP
GetNoSelect(bool * aResult)2945 nsMsgDBFolder::GetNoSelect(bool* aResult) {
2946   NS_ENSURE_ARG_POINTER(aResult);
2947   *aResult = false;
2948   return NS_OK;
2949 }
2950 
2951 NS_IMETHODIMP
GetImapShared(bool * aResult)2952 nsMsgDBFolder::GetImapShared(bool* aResult) {
2953   NS_ENSURE_ARG_POINTER(aResult);
2954   return GetFlag(nsMsgFolderFlags::PersonalShared, aResult);
2955 }
2956 
2957 NS_IMETHODIMP
GetCanSubscribe(bool * aResult)2958 nsMsgDBFolder::GetCanSubscribe(bool* aResult) {
2959   NS_ENSURE_ARG_POINTER(aResult);
2960   // by default, you can't subscribe.
2961   // if otherwise, override it.
2962   *aResult = false;
2963   return NS_OK;
2964 }
2965 
2966 NS_IMETHODIMP
GetCanFileMessages(bool * aResult)2967 nsMsgDBFolder::GetCanFileMessages(bool* aResult) {
2968   NS_ENSURE_ARG_POINTER(aResult);
2969 
2970   // varada - checking folder flag to see if it is the "Unsent Messages"
2971   // and if so return FALSE
2972   if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual)) {
2973     *aResult = false;
2974     return NS_OK;
2975   }
2976 
2977   bool isServer = false;
2978   nsresult rv = GetIsServer(&isServer);
2979   if (NS_FAILED(rv)) return rv;
2980 
2981   // by default, you can't file messages into servers, only to folders
2982   // if otherwise, override it.
2983   *aResult = !isServer;
2984   return NS_OK;
2985 }
2986 
2987 NS_IMETHODIMP
GetCanDeleteMessages(bool * aResult)2988 nsMsgDBFolder::GetCanDeleteMessages(bool* aResult) {
2989   NS_ENSURE_ARG_POINTER(aResult);
2990   *aResult = true;
2991   return NS_OK;
2992 }
2993 
2994 NS_IMETHODIMP
GetCanCreateSubfolders(bool * aResult)2995 nsMsgDBFolder::GetCanCreateSubfolders(bool* aResult) {
2996   NS_ENSURE_ARG_POINTER(aResult);
2997 
2998   // Checking folder flag to see if it is the "Unsent Messages"
2999   // or a virtual folder, and if so return FALSE
3000   if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual)) {
3001     *aResult = false;
3002     return NS_OK;
3003   }
3004 
3005   // by default, you can create subfolders on server and folders
3006   // if otherwise, override it.
3007   *aResult = true;
3008   return NS_OK;
3009 }
3010 
3011 NS_IMETHODIMP
GetCanRename(bool * aResult)3012 nsMsgDBFolder::GetCanRename(bool* aResult) {
3013   NS_ENSURE_ARG_POINTER(aResult);
3014 
3015   bool isServer = false;
3016   nsresult rv = GetIsServer(&isServer);
3017   if (NS_FAILED(rv)) return rv;
3018   // by default, you can't rename servers, only folders
3019   // if otherwise, override it.
3020   //
3021   // check if the folder is a special folder
3022   // (Trash, Drafts, Unsent Messages, Inbox, Sent, Templates, Junk, Archives)
3023   // if it is, don't allow the user to rename it
3024   // (which includes dnd moving it with in the same server)
3025   //
3026   // this errors on the side of caution.  we'll return false a lot
3027   // more often if we use flags,
3028   // instead of checking if the folder really is being used as a
3029   // special folder by looking at the "copies and folders" prefs on the
3030   // identities.
3031   *aResult = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
3032   return NS_OK;
3033 }
3034 
3035 NS_IMETHODIMP
GetCanCompact(bool * aResult)3036 nsMsgDBFolder::GetCanCompact(bool* aResult) {
3037   NS_ENSURE_ARG_POINTER(aResult);
3038   bool isServer = false;
3039   nsresult rv = GetIsServer(&isServer);
3040   NS_ENSURE_SUCCESS(rv, rv);
3041   // servers cannot be compacted --> 4.x
3042   // virtual search folders cannot be compacted
3043   *aResult = !isServer && !(mFlags & nsMsgFolderFlags::Virtual);
3044   // Check if the store supports compaction
3045   if (*aResult) {
3046     nsCOMPtr<nsIMsgPluggableStore> msgStore;
3047     GetMsgStore(getter_AddRefs(msgStore));
3048     if (msgStore) msgStore->GetSupportsCompaction(aResult);
3049   }
3050   return NS_OK;
3051 }
3052 
GetPrettyName(nsAString & name)3053 NS_IMETHODIMP nsMsgDBFolder::GetPrettyName(nsAString& name) {
3054   return GetName(name);
3055 }
3056 
3057 // -1: not retrieved yet, 1: English, 0: non-English.
3058 static int isEnglish = -1;
3059 
nonEnglishApp()3060 static bool nonEnglishApp() {
3061   if (isEnglish == -1) {
3062     nsAutoCString locale;
3063     mozilla::intl::LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale);
3064     isEnglish =
3065         (locale.EqualsLiteral("en") || StringBeginsWith(locale, "en-"_ns)) ? 1
3066                                                                            : 0;
3067   }
3068   return isEnglish ? false : true;
3069 }
3070 
hasTrashName(const nsAString & name)3071 static bool hasTrashName(const nsAString& name) {
3072   // Microsoft calls the folder "Deleted". If the application is non-English,
3073   // we want to use the localised name instead.
3074   return name.LowerCaseEqualsLiteral("trash") ||
3075          (name.LowerCaseEqualsLiteral("deleted") && nonEnglishApp());
3076 }
3077 
hasDraftsName(const nsAString & name)3078 static bool hasDraftsName(const nsAString& name) {
3079   // Some IMAP providers call the folder "Draft". If the application is
3080   // non-English, we want to use the localised name instead.
3081   return name.LowerCaseEqualsLiteral("drafts") ||
3082          (name.LowerCaseEqualsLiteral("draft") && nonEnglishApp());
3083 }
3084 
hasSentName(const nsAString & name)3085 static bool hasSentName(const nsAString& name) {
3086   // Some IMAP providers call the folder for sent messages "Outbox". That IMAP
3087   // folder is not related to Thunderbird's local folder for queued messages.
3088   // If we find such a folder with the 'SentMail' flag, we can safely localize
3089   // its name if the application is non-English.
3090   return name.LowerCaseEqualsLiteral("sent") ||
3091          (name.LowerCaseEqualsLiteral("outbox") && nonEnglishApp());
3092 }
3093 
SetPrettyName(const nsAString & name)3094 NS_IMETHODIMP nsMsgDBFolder::SetPrettyName(const nsAString& name) {
3095   nsresult rv;
3096 
3097   // Set pretty name only if special flag is set and if it the default folder
3098   // name
3099   if (mFlags & nsMsgFolderFlags::Inbox && name.LowerCaseEqualsLiteral("inbox"))
3100     rv = SetName(kLocalizedInboxName);
3101   else if (mFlags & nsMsgFolderFlags::SentMail && hasSentName(name))
3102     rv = SetName(kLocalizedSentName);
3103   else if (mFlags & nsMsgFolderFlags::Drafts && hasDraftsName(name))
3104     rv = SetName(kLocalizedDraftsName);
3105   else if (mFlags & nsMsgFolderFlags::Templates &&
3106            name.LowerCaseEqualsLiteral("templates"))
3107     rv = SetName(kLocalizedTemplatesName);
3108   else if (mFlags & nsMsgFolderFlags::Trash && hasTrashName(name))
3109     rv = SetName(kLocalizedTrashName);
3110   else if (mFlags & nsMsgFolderFlags::Queue &&
3111            name.LowerCaseEqualsLiteral("unsent messages"))
3112     rv = SetName(kLocalizedUnsentName);
3113   else if (mFlags & nsMsgFolderFlags::Junk &&
3114            name.LowerCaseEqualsLiteral("junk"))
3115     rv = SetName(kLocalizedJunkName);
3116   else if (mFlags & nsMsgFolderFlags::Archive &&
3117            name.LowerCaseEqualsLiteral("archives"))
3118     rv = SetName(kLocalizedArchivesName);
3119   else
3120     rv = SetName(name);
3121   return rv;
3122 }
3123 
GetName(nsAString & name)3124 NS_IMETHODIMP nsMsgDBFolder::GetName(nsAString& name) {
3125   nsresult rv;
3126   if (!mHaveParsedURI && mName.IsEmpty()) {
3127     rv = parseURI();
3128     if (NS_FAILED(rv)) return rv;
3129   }
3130 
3131   // if it's a server, just forward the call
3132   if (mIsServer) {
3133     nsCOMPtr<nsIMsgIncomingServer> server;
3134     rv = GetServer(getter_AddRefs(server));
3135     if (NS_SUCCEEDED(rv) && server) return server->GetPrettyName(name);
3136   }
3137 
3138   name = mName;
3139   return NS_OK;
3140 }
3141 
SetName(const nsAString & name)3142 NS_IMETHODIMP nsMsgDBFolder::SetName(const nsAString& name) {
3143   // override the URI-generated name
3144   if (!mName.Equals(name)) {
3145     mName = name;
3146     // old/new value doesn't matter here
3147     NotifyUnicharPropertyChanged(kName, name, name);
3148   }
3149   return NS_OK;
3150 }
3151 
3152 // For default, just return name
GetAbbreviatedName(nsAString & aAbbreviatedName)3153 NS_IMETHODIMP nsMsgDBFolder::GetAbbreviatedName(nsAString& aAbbreviatedName) {
3154   return GetName(aAbbreviatedName);
3155 }
3156 
3157 NS_IMETHODIMP
GetChildNamed(const nsAString & aName,nsIMsgFolder ** aChild)3158 nsMsgDBFolder::GetChildNamed(const nsAString& aName, nsIMsgFolder** aChild) {
3159   NS_ENSURE_ARG_POINTER(aChild);
3160   nsTArray<RefPtr<nsIMsgFolder>> dummy;
3161   GetSubFolders(dummy);  // initialize mSubFolders
3162   *aChild = nullptr;
3163 
3164   for (nsIMsgFolder* child : mSubFolders) {
3165     nsString folderName;
3166     nsresult rv = child->GetName(folderName);
3167     // case-insensitive compare is probably LCD across OS filesystems
3168     if (NS_SUCCEEDED(rv) &&
3169         folderName.Equals(aName, nsCaseInsensitiveStringComparator)) {
3170       NS_ADDREF(*aChild = child);
3171       return NS_OK;
3172     }
3173   }
3174   // don't return NS_OK if we didn't find the folder
3175   // see http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c15
3176   // and http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c17
3177   return NS_ERROR_FAILURE;
3178 }
3179 
GetChildWithURI(const nsACString & uri,bool deep,bool caseInsensitive,nsIMsgFolder ** child)3180 NS_IMETHODIMP nsMsgDBFolder::GetChildWithURI(const nsACString& uri, bool deep,
3181                                              bool caseInsensitive,
3182                                              nsIMsgFolder** child) {
3183   NS_ENSURE_ARG_POINTER(child);
3184   // will return nullptr if we can't find it
3185   *child = nullptr;
3186   nsTArray<RefPtr<nsIMsgFolder>> subFolders;
3187   nsresult rv = GetSubFolders(subFolders);
3188   NS_ENSURE_SUCCESS(rv, rv);
3189 
3190   for (nsIMsgFolder* folder : subFolders) {
3191     nsCString folderURI;
3192     rv = folder->GetURI(folderURI);
3193     NS_ENSURE_SUCCESS(rv, rv);
3194     bool equal =
3195         (caseInsensitive
3196              ? uri.Equals(folderURI, nsCaseInsensitiveCStringComparator)
3197              : uri.Equals(folderURI));
3198     if (equal) {
3199       NS_ADDREF(*child = folder);
3200       return NS_OK;
3201     }
3202     if (deep) {
3203       rv = folder->GetChildWithURI(uri, deep, caseInsensitive, child);
3204       if (NS_FAILED(rv)) return rv;
3205 
3206       if (*child) return NS_OK;
3207     }
3208   }
3209   return NS_OK;
3210 }
3211 
GetShowDeletedMessages(bool * showDeletedMessages)3212 NS_IMETHODIMP nsMsgDBFolder::GetShowDeletedMessages(bool* showDeletedMessages) {
3213   NS_ENSURE_ARG_POINTER(showDeletedMessages);
3214   *showDeletedMessages = false;
3215   return NS_OK;
3216 }
3217 
DeleteStorage()3218 NS_IMETHODIMP nsMsgDBFolder::DeleteStorage() {
3219   ForceDBClosed();
3220 
3221   // Delete the .msf file.
3222   // NOTE: this doesn't remove .msf files in subfolders, but
3223   // both nsMsgBrkMBoxStore::DeleteFolder() and
3224   // nsMsgMaildirStore::DeleteFolder() will remove those .msf files
3225   // as a side-effect of deleting the .sbd directory.
3226   nsCOMPtr<nsIFile> summaryFile;
3227   nsresult rv = GetSummaryFile(getter_AddRefs(summaryFile));
3228   NS_ENSURE_SUCCESS(rv, rv);
3229   bool exists = false;
3230   summaryFile->Exists(&exists);
3231   if (exists) {
3232     rv = summaryFile->Remove(false);
3233     NS_ENSURE_SUCCESS(rv, rv);
3234   }
3235 
3236   // Ask the msgStore to delete the actual storage (mbox, maildir or whatever
3237   // else may be supported in future).
3238   nsCOMPtr<nsIMsgPluggableStore> msgStore;
3239   rv = GetMsgStore(getter_AddRefs(msgStore));
3240   NS_ENSURE_SUCCESS(rv, rv);
3241   return msgStore->DeleteFolder(this);
3242 }
3243 
DeleteSelf(nsIMsgWindow * msgWindow)3244 NS_IMETHODIMP nsMsgDBFolder::DeleteSelf(nsIMsgWindow* msgWindow) {
3245   nsCOMPtr<nsIMsgFolder> parent;
3246   GetParent(getter_AddRefs(parent));
3247   if (!parent) {
3248     return NS_ERROR_FAILURE;
3249   }
3250   return parent->PropagateDelete(this, true, msgWindow);
3251 }
3252 
CreateStorageIfMissing(nsIUrlListener *)3253 NS_IMETHODIMP nsMsgDBFolder::CreateStorageIfMissing(
3254     nsIUrlListener* /* urlListener */) {
3255   NS_ASSERTION(false, "needs to be overridden");
3256   return NS_OK;
3257 }
3258 
PropagateDelete(nsIMsgFolder * folder,bool deleteStorage,nsIMsgWindow * msgWindow)3259 NS_IMETHODIMP nsMsgDBFolder::PropagateDelete(nsIMsgFolder* folder,
3260                                              bool deleteStorage,
3261                                              nsIMsgWindow* msgWindow) {
3262   // first, find the folder we're looking to delete
3263   nsresult rv = NS_OK;
3264 
3265   int32_t count = mSubFolders.Count();
3266   for (int32_t i = 0; i < count; i++) {
3267     nsCOMPtr<nsIMsgFolder> child(mSubFolders[i]);
3268     if (folder == child.get()) {
3269       // Remove self as parent
3270       child->SetParent(nullptr);
3271       // maybe delete disk storage for it, and its subfolders
3272       rv = child->RecursiveDelete(deleteStorage, msgWindow);
3273       if (NS_SUCCEEDED(rv)) {
3274         // Remove from list of subfolders.
3275         mSubFolders.RemoveObjectAt(i);
3276         NotifyItemRemoved(child);
3277         break;
3278       } else  // setting parent back if we failed
3279         child->SetParent(this);
3280     } else
3281       rv = child->PropagateDelete(folder, deleteStorage, msgWindow);
3282   }
3283 
3284   return rv;
3285 }
3286 
RecursiveDelete(bool deleteStorage,nsIMsgWindow * msgWindow)3287 NS_IMETHODIMP nsMsgDBFolder::RecursiveDelete(bool deleteStorage,
3288                                              nsIMsgWindow* msgWindow) {
3289   // If deleteStorage is true, recursively deletes disk storage for this folder
3290   // and all its subfolders.
3291   // Regardless of deleteStorage, always unlinks them from the children lists
3292   // and frees memory for the subfolders but NOT for _this_
3293   // and does not remove _this_ from the parent's list of children.
3294 
3295   nsresult rv = NS_OK;
3296 
3297   nsCOMPtr<nsIFile> dbPath;
3298   // first remove the deleted folder from the folder cache;
3299   nsresult result = GetFolderCacheKey(getter_AddRefs(dbPath));
3300 
3301   nsCOMPtr<nsIMsgAccountManager> accountMgr =
3302       do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &result);
3303   if (NS_SUCCEEDED(result)) {
3304     nsCOMPtr<nsIMsgFolderCache> folderCache;
3305     result = accountMgr->GetFolderCache(getter_AddRefs(folderCache));
3306     if (NS_SUCCEEDED(result) && folderCache) {
3307       nsCString persistentPath;
3308       result = dbPath->GetPersistentDescriptor(persistentPath);
3309       if (NS_SUCCEEDED(result)) folderCache->RemoveElement(persistentPath);
3310     }
3311   }
3312 
3313   int32_t count = mSubFolders.Count();
3314   while (count > 0) {
3315     nsIMsgFolder* child = mSubFolders[0];
3316 
3317     child->SetParent(nullptr);
3318     rv = child->RecursiveDelete(deleteStorage, msgWindow);
3319     if (NS_SUCCEEDED(rv))
3320       // unlink it from this child's list
3321       mSubFolders.RemoveObjectAt(0);
3322     else {
3323       // setting parent back if we failed for some reason
3324       child->SetParent(this);
3325       break;
3326     }
3327 
3328     count--;
3329   }
3330 
3331   // now delete the disk storage for _this_
3332   if (deleteStorage && NS_SUCCEEDED(rv)) {
3333     // All delete commands use deleteStorage = true, and local moves use false.
3334     // IMAP moves use true, leaving this here in the hope that bug 439108
3335     // works out.
3336     nsCOMPtr<nsIMsgFolderNotificationService> notifier(
3337         do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
3338     if (notifier) notifier->NotifyFolderDeleted(this);
3339     rv = DeleteStorage();
3340   }
3341   return rv;
3342 }
3343 
CreateSubfolder(const nsAString & folderName,nsIMsgWindow * msgWindow)3344 NS_IMETHODIMP nsMsgDBFolder::CreateSubfolder(const nsAString& folderName,
3345                                              nsIMsgWindow* msgWindow) {
3346   return NS_ERROR_NOT_IMPLEMENTED;
3347 }
3348 
AddSubfolder(const nsAString & name,nsIMsgFolder ** child)3349 NS_IMETHODIMP nsMsgDBFolder::AddSubfolder(const nsAString& name,
3350                                           nsIMsgFolder** child) {
3351   NS_ENSURE_ARG_POINTER(child);
3352 
3353   int32_t flags = 0;
3354   nsresult rv;
3355 
3356   nsAutoCString uri(mURI);
3357   uri.Append('/');
3358 
3359   // URI should use UTF-8
3360   // (see RFC2396 Uniform Resource Identifiers (URI): Generic Syntax)
3361   nsAutoCString escapedName;
3362   rv = NS_MsgEscapeEncodeURLPath(name, escapedName);
3363   NS_ENSURE_SUCCESS(rv, rv);
3364 
3365   // fix for #192780
3366   // if this is the root folder
3367   // make sure the the special folders
3368   // have the right uri.
3369   // on disk, host\INBOX should be a folder with the uri
3370   // mailbox://user@host/Inbox" as mailbox://user@host/Inbox !=
3371   // mailbox://user@host/INBOX
3372   nsCOMPtr<nsIMsgFolder> rootFolder;
3373   rv = GetRootFolder(getter_AddRefs(rootFolder));
3374   if (NS_SUCCEEDED(rv) && rootFolder &&
3375       (rootFolder.get() == (nsIMsgFolder*)this)) {
3376     if (escapedName.LowerCaseEqualsLiteral("inbox"))
3377       uri += "Inbox";
3378     else if (escapedName.LowerCaseEqualsLiteral("unsent%20messages"))
3379       uri += "Unsent%20Messages";
3380     else if (escapedName.LowerCaseEqualsLiteral("drafts"))
3381       uri += "Drafts";
3382     else if (escapedName.LowerCaseEqualsLiteral("trash"))
3383       uri += "Trash";
3384     else if (escapedName.LowerCaseEqualsLiteral("sent"))
3385       uri += "Sent";
3386     else if (escapedName.LowerCaseEqualsLiteral("templates"))
3387       uri += "Templates";
3388     else if (escapedName.LowerCaseEqualsLiteral("archives"))
3389       uri += "Archives";
3390     else
3391       uri += escapedName.get();
3392   } else
3393     uri += escapedName.get();
3394 
3395   nsCOMPtr<nsIMsgFolder> msgFolder;
3396   rv = GetChildWithURI(uri, false /*deep*/, true /*case Insensitive*/,
3397                        getter_AddRefs(msgFolder));
3398   if (NS_SUCCEEDED(rv) && msgFolder) return NS_MSG_FOLDER_EXISTS;
3399 
3400   nsCOMPtr<nsIMsgFolder> folder;
3401   rv = GetOrCreateFolder(uri, getter_AddRefs(folder));
3402   NS_ENSURE_SUCCESS(rv, rv);
3403 
3404   folder->GetFlags((uint32_t*)&flags);
3405   flags |= nsMsgFolderFlags::Mail;
3406   folder->SetParent(this);
3407 
3408   bool isServer;
3409   rv = GetIsServer(&isServer);
3410 
3411   // Only set these if these are top level children.
3412   if (NS_SUCCEEDED(rv) && isServer) {
3413     if (name.LowerCaseEqualsLiteral("inbox")) {
3414       flags |= nsMsgFolderFlags::Inbox;
3415       SetBiffState(nsIMsgFolder::nsMsgBiffState_Unknown);
3416     } else if (name.LowerCaseEqualsLiteral("trash"))
3417       flags |= nsMsgFolderFlags::Trash;
3418     else if (name.LowerCaseEqualsLiteral("unsent messages") ||
3419              name.LowerCaseEqualsLiteral("outbox"))
3420       flags |= nsMsgFolderFlags::Queue;
3421   }
3422 
3423   folder->SetFlags(flags);
3424 
3425   if (folder) mSubFolders.AppendObject(folder);
3426 
3427   folder.forget(child);
3428   // at this point we must be ok and we don't want to return failure in case
3429   // GetIsServer failed.
3430   return NS_OK;
3431 }
3432 
Compact(nsIUrlListener * aListener,nsIMsgWindow * aMsgWindow)3433 NS_IMETHODIMP nsMsgDBFolder::Compact(nsIUrlListener* aListener,
3434                                      nsIMsgWindow* aMsgWindow) {
3435   return NS_ERROR_NOT_IMPLEMENTED;
3436 }
3437 
CompactAll(nsIUrlListener * aListener,nsIMsgWindow * aMsgWindow,bool aCompactOfflineAlso)3438 NS_IMETHODIMP nsMsgDBFolder::CompactAll(nsIUrlListener* aListener,
3439                                         nsIMsgWindow* aMsgWindow,
3440                                         bool aCompactOfflineAlso) {
3441   NS_ASSERTION(false, "should be overridden by child class");
3442   return NS_ERROR_NOT_IMPLEMENTED;
3443 }
3444 
EmptyTrash(nsIMsgWindow * msgWindow,nsIUrlListener * aListener)3445 NS_IMETHODIMP nsMsgDBFolder::EmptyTrash(nsIMsgWindow* msgWindow,
3446                                         nsIUrlListener* aListener) {
3447   return NS_ERROR_NOT_IMPLEMENTED;
3448 }
3449 
CheckIfFolderExists(const nsAString & newFolderName,nsIMsgFolder * parentFolder,nsIMsgWindow * msgWindow)3450 nsresult nsMsgDBFolder::CheckIfFolderExists(const nsAString& newFolderName,
3451                                             nsIMsgFolder* parentFolder,
3452                                             nsIMsgWindow* msgWindow) {
3453   NS_ENSURE_ARG_POINTER(parentFolder);
3454   nsTArray<RefPtr<nsIMsgFolder>> subFolders;
3455   nsresult rv = parentFolder->GetSubFolders(subFolders);
3456   NS_ENSURE_SUCCESS(rv, rv);
3457 
3458   for (nsIMsgFolder* msgFolder : subFolders) {
3459     nsString folderName;
3460 
3461     msgFolder->GetName(folderName);
3462     if (folderName.Equals(newFolderName, nsCaseInsensitiveStringComparator)) {
3463       ThrowAlertMsg("folderExists", msgWindow);
3464       return NS_MSG_FOLDER_EXISTS;
3465     }
3466   }
3467   return NS_OK;
3468 }
3469 
ConfirmAutoFolderRename(nsIMsgWindow * msgWindow,const nsString & aOldName,const nsString & aNewName)3470 bool nsMsgDBFolder::ConfirmAutoFolderRename(nsIMsgWindow* msgWindow,
3471                                             const nsString& aOldName,
3472                                             const nsString& aNewName) {
3473   nsCOMPtr<nsIStringBundle> bundle;
3474   nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
3475   if (NS_WARN_IF(NS_FAILED(rv))) {
3476     return false;
3477   }
3478 
3479   nsString folderName;
3480   GetName(folderName);
3481   AutoTArray<nsString, 3> formatStrings = {aOldName, folderName, aNewName};
3482 
3483   nsString confirmString;
3484   rv = bundle->FormatStringFromName("confirmDuplicateFolderRename",
3485                                     formatStrings, confirmString);
3486   if (NS_WARN_IF(NS_FAILED(rv))) {
3487     return false;
3488   }
3489 
3490   bool confirmed = false;
3491   rv = ThrowConfirmationPrompt(msgWindow, confirmString, &confirmed);
3492   if (NS_WARN_IF(NS_FAILED(rv))) {
3493     return false;
3494   }
3495   return confirmed;
3496 }
3497 
AddDirectorySeparator(nsIFile * path)3498 nsresult nsMsgDBFolder::AddDirectorySeparator(nsIFile* path) {
3499   nsAutoString leafName;
3500   path->GetLeafName(leafName);
3501   leafName.AppendLiteral(FOLDER_SUFFIX);
3502   return path->SetLeafName(leafName);
3503 }
3504 
3505 /* Finds the directory associated with this folder.  That is if the path is
3506    c:\Inbox, it will return c:\Inbox.sbd if it succeeds.  If that path doesn't
3507    currently exist then it will create it. Path is strictly an out parameter.
3508   */
CreateDirectoryForFolder(nsIFile ** resultFile)3509 nsresult nsMsgDBFolder::CreateDirectoryForFolder(nsIFile** resultFile) {
3510   nsresult rv = NS_OK;
3511 
3512   nsCOMPtr<nsIFile> path;
3513   rv = GetFilePath(getter_AddRefs(path));
3514   if (NS_FAILED(rv)) return rv;
3515 
3516   bool pathIsDirectory = false;
3517   path->IsDirectory(&pathIsDirectory);
3518 
3519   bool isServer;
3520   GetIsServer(&isServer);
3521 
3522   // Make sure this is REALLY the parent for subdirectories
3523   if (pathIsDirectory && !isServer) {
3524     nsAutoString leafName;
3525     path->GetLeafName(leafName);
3526     nsAutoString ext;
3527     int32_t idx = leafName.RFindChar('.');
3528     if (idx != -1) ext = Substring(leafName, idx);
3529     if (!ext.EqualsLiteral(
3530             FOLDER_SUFFIX8))  // No overload for char16_t available.
3531       pathIsDirectory = false;
3532   }
3533 
3534   if (!pathIsDirectory) {
3535     // If the current path isn't a directory, add directory separator
3536     // and test it out.
3537     rv = AddDirectorySeparator(path);
3538     if (NS_FAILED(rv)) return rv;
3539 
3540     // If that doesn't exist, then we have to create this directory
3541     pathIsDirectory = false;
3542     path->IsDirectory(&pathIsDirectory);
3543     if (!pathIsDirectory) {
3544       bool pathExists;
3545       path->Exists(&pathExists);
3546       // If for some reason there's a file with the directory separator
3547       // then we are going to fail.
3548       rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY
3549                       : path->Create(nsIFile::DIRECTORY_TYPE, 0700);
3550     }
3551   }
3552   if (NS_SUCCEEDED(rv)) path.forget(resultFile);
3553   return rv;
3554 }
3555 
3556 /* Finds the backup directory associated with this folder, stored on the temp
3557    drive. If that path doesn't currently exist then it will create it. Path is
3558    strictly an out parameter.
3559   */
CreateBackupDirectory(nsIFile ** resultFile)3560 nsresult nsMsgDBFolder::CreateBackupDirectory(nsIFile** resultFile) {
3561   nsCOMPtr<nsIFile> path;
3562   nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(path));
3563   NS_ENSURE_SUCCESS(rv, rv);
3564 
3565   rv = path->Append(u"MozillaMailnews"_ns);
3566   bool pathIsDirectory;
3567   path->IsDirectory(&pathIsDirectory);
3568 
3569   // If that doesn't exist, then we have to create this directory
3570   if (!pathIsDirectory) {
3571     bool pathExists;
3572     path->Exists(&pathExists);
3573     // If for some reason there's a file with the directory separator
3574     // then we are going to fail.
3575     rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY
3576                     : path->Create(nsIFile::DIRECTORY_TYPE, 0700);
3577   }
3578   if (NS_SUCCEEDED(rv)) path.forget(resultFile);
3579   return rv;
3580 }
3581 
GetBackupSummaryFile(nsIFile ** aBackupFile,const nsACString & newName)3582 nsresult nsMsgDBFolder::GetBackupSummaryFile(nsIFile** aBackupFile,
3583                                              const nsACString& newName) {
3584   nsCOMPtr<nsIFile> backupDir;
3585   nsresult rv = CreateBackupDirectory(getter_AddRefs(backupDir));
3586   NS_ENSURE_SUCCESS(rv, rv);
3587 
3588   // We use a dummy message folder file so we can use
3589   // GetSummaryFileLocation to get the db file name
3590   nsCOMPtr<nsIFile> backupDBDummyFolder;
3591   rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
3592   NS_ENSURE_SUCCESS(rv, rv);
3593 
3594   if (!newName.IsEmpty()) {
3595     rv = backupDBDummyFolder->AppendNative(newName);
3596   } else  // if newName is null, use the folder name
3597   {
3598     nsCOMPtr<nsIFile> folderPath;
3599     rv = GetFilePath(getter_AddRefs(folderPath));
3600     NS_ENSURE_SUCCESS(rv, rv);
3601 
3602     nsAutoCString folderName;
3603     rv = folderPath->GetNativeLeafName(folderName);
3604     NS_ENSURE_SUCCESS(rv, rv);
3605     rv = backupDBDummyFolder->AppendNative(folderName);
3606   }
3607   NS_ENSURE_SUCCESS(rv, rv);
3608 
3609   nsCOMPtr<nsIFile> backupDBFile;
3610   rv =
3611       GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile));
3612   NS_ENSURE_SUCCESS(rv, rv);
3613 
3614   backupDBFile.forget(aBackupFile);
3615   return NS_OK;
3616 }
3617 
Rename(const nsAString & aNewName,nsIMsgWindow * msgWindow)3618 NS_IMETHODIMP nsMsgDBFolder::Rename(const nsAString& aNewName,
3619                                     nsIMsgWindow* msgWindow) {
3620   nsCOMPtr<nsIFile> oldPathFile;
3621   nsresult rv = GetFilePath(getter_AddRefs(oldPathFile));
3622   if (NS_FAILED(rv)) return rv;
3623   nsCOMPtr<nsIMsgFolder> parentFolder;
3624   rv = GetParent(getter_AddRefs(parentFolder));
3625   if (!parentFolder) return NS_ERROR_FAILURE;
3626   nsCOMPtr<nsISupports> parentSupport = do_QueryInterface(parentFolder);
3627   nsCOMPtr<nsIFile> oldSummaryFile;
3628   rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile));
3629   NS_ENSURE_SUCCESS(rv, rv);
3630 
3631   nsCOMPtr<nsIFile> dirFile;
3632   int32_t count = mSubFolders.Count();
3633 
3634   if (count > 0) rv = CreateDirectoryForFolder(getter_AddRefs(dirFile));
3635 
3636   nsAutoString newDiskName(aNewName);
3637   NS_MsgHashIfNecessary(newDiskName);
3638 
3639   if (mName.Equals(aNewName, nsCaseInsensitiveStringComparator)) {
3640     rv = ThrowAlertMsg("folderExists", msgWindow);
3641     return NS_MSG_FOLDER_EXISTS;
3642   } else {
3643     nsCOMPtr<nsIFile> parentPathFile;
3644     parentFolder->GetFilePath(getter_AddRefs(parentPathFile));
3645     NS_ENSURE_SUCCESS(rv, rv);
3646     bool isDirectory = false;
3647     parentPathFile->IsDirectory(&isDirectory);
3648     if (!isDirectory) AddDirectorySeparator(parentPathFile);
3649 
3650     rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow);
3651     if (NS_FAILED(rv)) return rv;
3652   }
3653 
3654   ForceDBClosed();
3655 
3656   // Save of dir name before appending .msf
3657   nsAutoString newNameDirStr(newDiskName);
3658 
3659   if (!(mFlags & nsMsgFolderFlags::Virtual))
3660     rv = oldPathFile->MoveTo(nullptr, newDiskName);
3661   if (NS_SUCCEEDED(rv)) {
3662     newDiskName.AppendLiteral(SUMMARY_SUFFIX);
3663     oldSummaryFile->MoveTo(nullptr, newDiskName);
3664   } else {
3665     ThrowAlertMsg("folderRenameFailed", msgWindow);
3666     return rv;
3667   }
3668 
3669   if (NS_SUCCEEDED(rv) && count > 0) {
3670     // rename "*.sbd" directory
3671     newNameDirStr.AppendLiteral(FOLDER_SUFFIX);
3672     dirFile->MoveTo(nullptr, newNameDirStr);
3673   }
3674 
3675   nsCOMPtr<nsIMsgFolder> newFolder;
3676   if (parentSupport) {
3677     rv = parentFolder->AddSubfolder(aNewName, getter_AddRefs(newFolder));
3678     if (newFolder) {
3679       newFolder->SetPrettyName(EmptyString());
3680       newFolder->SetPrettyName(aNewName);
3681       newFolder->SetFlags(mFlags);
3682       bool changed = false;
3683       MatchOrChangeFilterDestination(newFolder, true /*case-insensitive*/,
3684                                      &changed);
3685       if (changed) AlertFilterChanged(msgWindow);
3686 
3687       if (count > 0) newFolder->RenameSubFolders(msgWindow, this);
3688 
3689       if (parentFolder) {
3690         SetParent(nullptr);
3691         parentFolder->PropagateDelete(this, false, msgWindow);
3692         parentFolder->NotifyItemAdded(newFolder);
3693       }
3694       newFolder->NotifyFolderEvent(kRenameCompleted);
3695     }
3696   }
3697   return rv;
3698 }
3699 
RenameSubFolders(nsIMsgWindow * msgWindow,nsIMsgFolder * oldFolder)3700 NS_IMETHODIMP nsMsgDBFolder::RenameSubFolders(nsIMsgWindow* msgWindow,
3701                                               nsIMsgFolder* oldFolder) {
3702   return NS_ERROR_NOT_IMPLEMENTED;
3703 }
3704 
ContainsChildNamed(const nsAString & name,bool * containsChild)3705 NS_IMETHODIMP nsMsgDBFolder::ContainsChildNamed(const nsAString& name,
3706                                                 bool* containsChild) {
3707   NS_ENSURE_ARG_POINTER(containsChild);
3708   nsCOMPtr<nsIMsgFolder> child;
3709   GetChildNamed(name, getter_AddRefs(child));
3710   *containsChild = child != nullptr;
3711   return NS_OK;
3712 }
3713 
IsAncestorOf(nsIMsgFolder * child,bool * isAncestor)3714 NS_IMETHODIMP nsMsgDBFolder::IsAncestorOf(nsIMsgFolder* child,
3715                                           bool* isAncestor) {
3716   NS_ENSURE_ARG_POINTER(isAncestor);
3717   nsresult rv = NS_OK;
3718 
3719   int32_t count = mSubFolders.Count();
3720 
3721   for (int32_t i = 0; i < count; i++) {
3722     nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
3723     if (folder.get() == child)
3724       *isAncestor = true;
3725     else
3726       folder->IsAncestorOf(child, isAncestor);
3727 
3728     if (*isAncestor) return NS_OK;
3729   }
3730   *isAncestor = false;
3731   return rv;
3732 }
3733 
GenerateUniqueSubfolderName(const nsAString & prefix,nsIMsgFolder * otherFolder,nsAString & name)3734 NS_IMETHODIMP nsMsgDBFolder::GenerateUniqueSubfolderName(
3735     const nsAString& prefix, nsIMsgFolder* otherFolder, nsAString& name) {
3736   /* only try 256 times */
3737   for (int count = 0; count < 256; count++) {
3738     nsAutoString uniqueName;
3739     uniqueName.Assign(prefix);
3740     uniqueName.AppendInt(count);
3741     bool containsChild;
3742     bool otherContainsChild = false;
3743     ContainsChildNamed(uniqueName, &containsChild);
3744     if (otherFolder)
3745       otherFolder->ContainsChildNamed(uniqueName, &otherContainsChild);
3746 
3747     if (!containsChild && !otherContainsChild) {
3748       name = uniqueName;
3749       break;
3750     }
3751   }
3752   return NS_OK;
3753 }
3754 
UpdateSummaryTotals(bool force)3755 NS_IMETHODIMP nsMsgDBFolder::UpdateSummaryTotals(bool force) {
3756   if (!mNotifyCountChanges) return NS_OK;
3757 
3758   int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
3759   int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
3760   // We need to read this info from the database
3761   nsresult rv = ReadDBFolderInfo(force);
3762 
3763   if (NS_SUCCEEDED(rv)) {
3764     int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
3765     int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
3766 
3767     // Need to notify listeners that total count changed.
3768     if (oldTotalMessages != newTotalMessages)
3769       NotifyIntPropertyChanged(kTotalMessages, oldTotalMessages,
3770                                newTotalMessages);
3771 
3772     if (oldUnreadMessages != newUnreadMessages)
3773       NotifyIntPropertyChanged(kTotalUnreadMessages, oldUnreadMessages,
3774                                newUnreadMessages);
3775 
3776     FlushToFolderCache();
3777   }
3778   return rv;
3779 }
3780 
SummaryChanged()3781 NS_IMETHODIMP nsMsgDBFolder::SummaryChanged() {
3782   UpdateSummaryTotals(false);
3783   return NS_OK;
3784 }
3785 
GetNumUnread(bool deep,int32_t * numUnread)3786 NS_IMETHODIMP nsMsgDBFolder::GetNumUnread(bool deep, int32_t* numUnread) {
3787   NS_ENSURE_ARG_POINTER(numUnread);
3788 
3789   bool isServer = false;
3790   nsresult rv = GetIsServer(&isServer);
3791   NS_ENSURE_SUCCESS(rv, rv);
3792   int32_t total = isServer ? 0 : mNumUnreadMessages + mNumPendingUnreadMessages;
3793 
3794   if (deep) {
3795     if (total < 0)  // deep search never returns negative counts
3796       total = 0;
3797     int32_t count = mSubFolders.Count();
3798     for (int32_t i = 0; i < count; i++) {
3799       nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
3800       int32_t num;
3801       uint32_t folderFlags;
3802       folder->GetFlags(&folderFlags);
3803       if (!(folderFlags & nsMsgFolderFlags::Virtual)) {
3804         folder->GetNumUnread(deep, &num);
3805         total += num;
3806       }
3807     }
3808   }
3809   *numUnread = total;
3810   return NS_OK;
3811 }
3812 
GetTotalMessages(bool deep,int32_t * totalMessages)3813 NS_IMETHODIMP nsMsgDBFolder::GetTotalMessages(bool deep,
3814                                               int32_t* totalMessages) {
3815   NS_ENSURE_ARG_POINTER(totalMessages);
3816 
3817   bool isServer = false;
3818   nsresult rv = GetIsServer(&isServer);
3819   NS_ENSURE_SUCCESS(rv, rv);
3820   int32_t total = isServer ? 0 : mNumTotalMessages + mNumPendingTotalMessages;
3821 
3822   if (deep) {
3823     if (total < 0)  // deep search never returns negative counts
3824       total = 0;
3825     int32_t count = mSubFolders.Count();
3826     for (int32_t i = 0; i < count; i++) {
3827       nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
3828       int32_t num;
3829       uint32_t folderFlags;
3830       folder->GetFlags(&folderFlags);
3831       if (!(folderFlags & nsMsgFolderFlags::Virtual)) {
3832         folder->GetTotalMessages(deep, &num);
3833         total += num;
3834       }
3835     }
3836   }
3837   *totalMessages = total;
3838   return NS_OK;
3839 }
3840 
GetNumPendingUnread(int32_t * aPendingUnread)3841 NS_IMETHODIMP nsMsgDBFolder::GetNumPendingUnread(int32_t* aPendingUnread) {
3842   *aPendingUnread = mNumPendingUnreadMessages;
3843   return NS_OK;
3844 }
3845 
GetNumPendingTotalMessages(int32_t * aPendingTotal)3846 NS_IMETHODIMP nsMsgDBFolder::GetNumPendingTotalMessages(
3847     int32_t* aPendingTotal) {
3848   *aPendingTotal = mNumPendingTotalMessages;
3849   return NS_OK;
3850 }
3851 
ChangeNumPendingUnread(int32_t delta)3852 NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingUnread(int32_t delta) {
3853   if (delta) {
3854     int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
3855     mNumPendingUnreadMessages += delta;
3856     int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
3857     NS_ASSERTION(newUnreadMessages >= 0,
3858                  "shouldn't have negative unread message count");
3859     if (newUnreadMessages >= 0) {
3860       nsCOMPtr<nsIMsgDatabase> db;
3861       nsCOMPtr<nsIDBFolderInfo> folderInfo;
3862       nsresult rv =
3863           GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
3864       if (NS_SUCCEEDED(rv) && folderInfo)
3865         folderInfo->SetImapUnreadPendingMessages(mNumPendingUnreadMessages);
3866       NotifyIntPropertyChanged(kTotalUnreadMessages, oldUnreadMessages,
3867                                newUnreadMessages);
3868     }
3869   }
3870   return NS_OK;
3871 }
3872 
ChangeNumPendingTotalMessages(int32_t delta)3873 NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingTotalMessages(int32_t delta) {
3874   if (delta) {
3875     int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
3876     mNumPendingTotalMessages += delta;
3877     int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
3878 
3879     nsCOMPtr<nsIMsgDatabase> db;
3880     nsCOMPtr<nsIDBFolderInfo> folderInfo;
3881     nsresult rv =
3882         GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
3883     if (NS_SUCCEEDED(rv) && folderInfo)
3884       folderInfo->SetImapTotalPendingMessages(mNumPendingTotalMessages);
3885     NotifyIntPropertyChanged(kTotalMessages, oldTotalMessages,
3886                              newTotalMessages);
3887   }
3888   return NS_OK;
3889 }
3890 
SetFlag(uint32_t flag)3891 NS_IMETHODIMP nsMsgDBFolder::SetFlag(uint32_t flag) {
3892   // If calling this function causes us to open the db (i.e., it was not
3893   // open before), we're going to close the db before returning.
3894   bool dbWasOpen = mDatabase != nullptr;
3895 
3896   ReadDBFolderInfo(false);
3897   // OnFlagChange can be expensive, so don't call it if we don't need to
3898   bool flagSet;
3899   nsresult rv;
3900 
3901   if (NS_FAILED(rv = GetFlag(flag, &flagSet))) return rv;
3902 
3903   if (!flagSet) {
3904     mFlags |= flag;
3905     OnFlagChange(flag);
3906   }
3907   if (!dbWasOpen && mDatabase) SetMsgDatabase(nullptr);
3908 
3909   return NS_OK;
3910 }
3911 
ClearFlag(uint32_t flag)3912 NS_IMETHODIMP nsMsgDBFolder::ClearFlag(uint32_t flag) {
3913   // OnFlagChange can be expensive, so don't call it if we don't need to
3914   bool flagSet;
3915   nsresult rv;
3916 
3917   if (NS_FAILED(rv = GetFlag(flag, &flagSet))) return rv;
3918 
3919   if (flagSet) {
3920     mFlags &= ~flag;
3921     OnFlagChange(flag);
3922   }
3923 
3924   return NS_OK;
3925 }
3926 
GetFlag(uint32_t flag,bool * _retval)3927 NS_IMETHODIMP nsMsgDBFolder::GetFlag(uint32_t flag, bool* _retval) {
3928   *_retval = ((mFlags & flag) != 0);
3929   return NS_OK;
3930 }
3931 
ToggleFlag(uint32_t flag)3932 NS_IMETHODIMP nsMsgDBFolder::ToggleFlag(uint32_t flag) {
3933   mFlags ^= flag;
3934   OnFlagChange(flag);
3935 
3936   return NS_OK;
3937 }
3938 
OnFlagChange(uint32_t flag)3939 NS_IMETHODIMP nsMsgDBFolder::OnFlagChange(uint32_t flag) {
3940   nsresult rv = NS_OK;
3941   nsCOMPtr<nsIMsgDatabase> db;
3942   nsCOMPtr<nsIDBFolderInfo> folderInfo;
3943   rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
3944   if (NS_SUCCEEDED(rv) && folderInfo) {
3945 #ifdef DEBUG_bienvenu1
3946     nsString name;
3947     rv = GetName(name);
3948     NS_ASSERTION(Compare(name, kLocalizedTrashName) ||
3949                      (mFlags & nsMsgFolderFlags::Trash),
3950                  "lost trash flag");
3951 #endif
3952     folderInfo->SetFlags((int32_t)mFlags);
3953     if (db) db->Commit(nsMsgDBCommitType::kLargeCommit);
3954 
3955     if (mFlags & flag)
3956       NotifyIntPropertyChanged(kFolderFlag, mFlags & ~flag, mFlags);
3957     else
3958       NotifyIntPropertyChanged(kFolderFlag, mFlags | flag, mFlags);
3959 
3960     if (flag & nsMsgFolderFlags::Offline) {
3961       bool newValue = mFlags & nsMsgFolderFlags::Offline;
3962       rv = NotifyBoolPropertyChanged(kSynchronize, !newValue, !!newValue);
3963     } else if (flag & nsMsgFolderFlags::Elided) {
3964       bool newValue = mFlags & nsMsgFolderFlags::Elided;
3965       rv = NotifyBoolPropertyChanged(kOpen, !!newValue, !newValue);
3966     }
3967   }
3968   return rv;
3969 }
3970 
SetFlags(uint32_t aFlags)3971 NS_IMETHODIMP nsMsgDBFolder::SetFlags(uint32_t aFlags) {
3972   if (mFlags != aFlags) {
3973     uint32_t changedFlags = aFlags ^ mFlags;
3974     mFlags = aFlags;
3975     OnFlagChange(changedFlags);
3976   }
3977   return NS_OK;
3978 }
3979 
GetFolderWithFlags(uint32_t aFlags,nsIMsgFolder ** aResult)3980 NS_IMETHODIMP nsMsgDBFolder::GetFolderWithFlags(uint32_t aFlags,
3981                                                 nsIMsgFolder** aResult) {
3982   if ((mFlags & aFlags) == aFlags) {
3983     NS_ADDREF(*aResult = this);
3984     return NS_OK;
3985   }
3986 
3987   nsTArray<RefPtr<nsIMsgFolder>> dummy;
3988   GetSubFolders(dummy);  // initialize mSubFolders
3989 
3990   int32_t count = mSubFolders.Count();
3991   *aResult = nullptr;
3992   for (int32_t i = 0; !*aResult && i < count; ++i)
3993     mSubFolders[i]->GetFolderWithFlags(aFlags, aResult);
3994 
3995   return NS_OK;
3996 }
3997 
GetFoldersWithFlags(uint32_t aFlags,nsTArray<RefPtr<nsIMsgFolder>> & aResult)3998 NS_IMETHODIMP nsMsgDBFolder::GetFoldersWithFlags(
3999     uint32_t aFlags, nsTArray<RefPtr<nsIMsgFolder>>& aResult) {
4000   aResult.Clear();
4001 
4002   // Ensure initialisation of mSubFolders.
4003   nsTArray<RefPtr<nsIMsgFolder>> dummy;
4004   GetSubFolders(dummy);
4005 
4006   if ((mFlags & aFlags) == aFlags) {
4007     aResult.AppendElement(this);
4008   }
4009 
4010   // Recurse down through children.
4011   for (nsIMsgFolder* child : mSubFolders) {
4012     nsTArray<RefPtr<nsIMsgFolder>> subMatches;
4013     child->GetFoldersWithFlags(aFlags, subMatches);
4014     aResult.AppendElements(subMatches);
4015   }
4016   return NS_OK;
4017 }
4018 
IsSpecialFolder(uint32_t aFlags,bool aCheckAncestors,bool * aIsSpecial)4019 NS_IMETHODIMP nsMsgDBFolder::IsSpecialFolder(uint32_t aFlags,
4020                                              bool aCheckAncestors,
4021                                              bool* aIsSpecial) {
4022   NS_ENSURE_ARG_POINTER(aIsSpecial);
4023 
4024   if ((mFlags & aFlags) == 0) {
4025     nsCOMPtr<nsIMsgFolder> parentMsgFolder;
4026     GetParent(getter_AddRefs(parentMsgFolder));
4027 
4028     if (parentMsgFolder && aCheckAncestors)
4029       parentMsgFolder->IsSpecialFolder(aFlags, aCheckAncestors, aIsSpecial);
4030     else
4031       *aIsSpecial = false;
4032   } else {
4033     // The user can set their INBOX to be their SENT folder.
4034     // in that case, we want this folder to act like an INBOX,
4035     // and not a SENT folder
4036     *aIsSpecial = !((aFlags & nsMsgFolderFlags::SentMail) &&
4037                     (mFlags & nsMsgFolderFlags::Inbox));
4038   }
4039   return NS_OK;
4040 }
4041 
GetDeletable(bool * deletable)4042 NS_IMETHODIMP nsMsgDBFolder::GetDeletable(bool* deletable) {
4043   NS_ENSURE_ARG_POINTER(deletable);
4044   *deletable = false;
4045   return NS_OK;
4046 }
4047 
GetDisplayRecipients(bool * displayRecipients)4048 NS_IMETHODIMP nsMsgDBFolder::GetDisplayRecipients(bool* displayRecipients) {
4049   *displayRecipients = false;
4050   if (mFlags & nsMsgFolderFlags::SentMail &&
4051       !(mFlags & nsMsgFolderFlags::Inbox))
4052     *displayRecipients = true;
4053   else if (mFlags & nsMsgFolderFlags::Queue)
4054     *displayRecipients = true;
4055   return NS_OK;
4056 }
4057 
AcquireSemaphore(nsISupports * semHolder)4058 NS_IMETHODIMP nsMsgDBFolder::AcquireSemaphore(nsISupports* semHolder) {
4059   nsresult rv = NS_OK;
4060   if (mSemaphoreHolder == NULL)
4061     mSemaphoreHolder = semHolder;  // Don't AddRef due to ownership issues.
4062   else
4063     rv = NS_MSG_FOLDER_BUSY;
4064   return rv;
4065 }
4066 
ReleaseSemaphore(nsISupports * semHolder)4067 NS_IMETHODIMP nsMsgDBFolder::ReleaseSemaphore(nsISupports* semHolder) {
4068   if (!mSemaphoreHolder || mSemaphoreHolder == semHolder)
4069     mSemaphoreHolder = NULL;
4070   return NS_OK;
4071 }
4072 
TestSemaphore(nsISupports * semHolder,bool * result)4073 NS_IMETHODIMP nsMsgDBFolder::TestSemaphore(nsISupports* semHolder,
4074                                            bool* result) {
4075   NS_ENSURE_ARG_POINTER(result);
4076   *result = (mSemaphoreHolder == semHolder);
4077   return NS_OK;
4078 }
4079 
GetLocked(bool * isLocked)4080 NS_IMETHODIMP nsMsgDBFolder::GetLocked(bool* isLocked) {
4081   *isLocked = mSemaphoreHolder != NULL;
4082   return NS_OK;
4083 }
4084 
GetRelativePathName(nsACString & pathName)4085 NS_IMETHODIMP nsMsgDBFolder::GetRelativePathName(nsACString& pathName) {
4086   pathName.Truncate();
4087   return NS_OK;
4088 }
4089 
GetSizeOnDisk(int64_t * size)4090 NS_IMETHODIMP nsMsgDBFolder::GetSizeOnDisk(int64_t* size) {
4091   NS_ENSURE_ARG_POINTER(size);
4092   *size = kSizeUnknown;
4093   return NS_OK;
4094 }
4095 
SetSizeOnDisk(int64_t aSizeOnDisk)4096 NS_IMETHODIMP nsMsgDBFolder::SetSizeOnDisk(int64_t aSizeOnDisk) {
4097   NotifyIntPropertyChanged(kFolderSize, mFolderSize, aSizeOnDisk);
4098   mFolderSize = aSizeOnDisk;
4099   return NS_OK;
4100 }
4101 
GetUsername(nsACString & userName)4102 NS_IMETHODIMP nsMsgDBFolder::GetUsername(nsACString& userName) {
4103   nsresult rv;
4104   nsCOMPtr<nsIMsgIncomingServer> server;
4105   rv = GetServer(getter_AddRefs(server));
4106   NS_ENSURE_SUCCESS(rv, rv);
4107   return server->GetUsername(userName);
4108 }
4109 
GetHostname(nsACString & hostName)4110 NS_IMETHODIMP nsMsgDBFolder::GetHostname(nsACString& hostName) {
4111   nsresult rv;
4112   nsCOMPtr<nsIMsgIncomingServer> server;
4113   rv = GetServer(getter_AddRefs(server));
4114   NS_ENSURE_SUCCESS(rv, rv);
4115   return server->GetHostName(hostName);
4116 }
4117 
GetNewMessages(nsIMsgWindow *,nsIUrlListener *)4118 NS_IMETHODIMP nsMsgDBFolder::GetNewMessages(nsIMsgWindow*,
4119                                             nsIUrlListener* /* aListener */) {
4120   return NS_ERROR_NOT_IMPLEMENTED;
4121 }
4122 
GetBiffState(uint32_t * aBiffState)4123 NS_IMETHODIMP nsMsgDBFolder::GetBiffState(uint32_t* aBiffState) {
4124   nsCOMPtr<nsIMsgIncomingServer> server;
4125   nsresult rv = GetServer(getter_AddRefs(server));
4126   NS_ENSURE_SUCCESS(rv, rv);
4127   return server->GetBiffState(aBiffState);
4128 }
4129 
SetBiffState(uint32_t aBiffState)4130 NS_IMETHODIMP nsMsgDBFolder::SetBiffState(uint32_t aBiffState) {
4131   uint32_t oldBiffState = nsMsgBiffState_Unknown;
4132   nsCOMPtr<nsIMsgIncomingServer> server;
4133   nsresult rv = GetServer(getter_AddRefs(server));
4134   if (NS_SUCCEEDED(rv) && server) rv = server->GetBiffState(&oldBiffState);
4135 
4136   if (oldBiffState != aBiffState) {
4137     // Get the server and notify it and not inbox.
4138     if (!mIsServer) {
4139       nsCOMPtr<nsIMsgFolder> folder;
4140       rv = GetRootFolder(getter_AddRefs(folder));
4141       if (NS_SUCCEEDED(rv) && folder) return folder->SetBiffState(aBiffState);
4142     }
4143     if (server) server->SetBiffState(aBiffState);
4144 
4145     NotifyIntPropertyChanged(kBiffState, oldBiffState, aBiffState);
4146   } else if (aBiffState == oldBiffState &&
4147              aBiffState == nsMsgBiffState_NewMail) {
4148     // The folder has been updated, so update the MRUTime
4149     SetMRUTime();
4150     // biff is already set, but notify that there is additional new mail for the
4151     // folder
4152     NotifyIntPropertyChanged(kNewMailReceived, 0, mNumNewBiffMessages);
4153   } else if (aBiffState == nsMsgBiffState_NoMail) {
4154     // even if the old biff state equals the new biff state, it is still
4155     // possible that we've never cleared the number of new messages for this
4156     // particular folder. This happens when the new mail state got cleared by
4157     // viewing a new message in folder that is different from this one. Biff
4158     // state is stored per server
4159     //  the num. of new messages is per folder.
4160     SetNumNewMessages(0);
4161   }
4162   return NS_OK;
4163 }
4164 
GetNumNewMessages(bool deep,int32_t * aNumNewMessages)4165 NS_IMETHODIMP nsMsgDBFolder::GetNumNewMessages(bool deep,
4166                                                int32_t* aNumNewMessages) {
4167   NS_ENSURE_ARG_POINTER(aNumNewMessages);
4168 
4169   int32_t numNewMessages = (!deep || !(mFlags & nsMsgFolderFlags::Virtual))
4170                                ? mNumNewBiffMessages
4171                                : 0;
4172   if (deep) {
4173     int32_t count = mSubFolders.Count();
4174     for (int32_t i = 0; i < count; i++) {
4175       int32_t num;
4176       mSubFolders[i]->GetNumNewMessages(deep, &num);
4177       if (num > 0)  // it's legal for counts to be negative if we don't know
4178         numNewMessages += num;
4179     }
4180   }
4181   *aNumNewMessages = numNewMessages;
4182   return NS_OK;
4183 }
4184 
SetNumNewMessages(int32_t aNumNewMessages)4185 NS_IMETHODIMP nsMsgDBFolder::SetNumNewMessages(int32_t aNumNewMessages) {
4186   if (aNumNewMessages != mNumNewBiffMessages) {
4187     int32_t oldNumMessages = mNumNewBiffMessages;
4188     mNumNewBiffMessages = aNumNewMessages;
4189 
4190     nsAutoCString oldNumMessagesStr;
4191     oldNumMessagesStr.AppendInt(oldNumMessages);
4192     nsAutoCString newNumMessagesStr;
4193     newNumMessagesStr.AppendInt(aNumNewMessages);
4194     NotifyPropertyChanged(kNumNewBiffMessages, oldNumMessagesStr,
4195                           newNumMessagesStr);
4196   }
4197   return NS_OK;
4198 }
4199 
GetRootFolder(nsIMsgFolder ** aRootFolder)4200 NS_IMETHODIMP nsMsgDBFolder::GetRootFolder(nsIMsgFolder** aRootFolder) {
4201   NS_ENSURE_ARG_POINTER(aRootFolder);
4202   nsresult rv;
4203   nsCOMPtr<nsIMsgIncomingServer> server;
4204   rv = GetServer(getter_AddRefs(server));
4205   NS_ENSURE_SUCCESS(rv, rv);
4206   return server->GetRootMsgFolder(aRootFolder);
4207 }
4208 
4209 NS_IMETHODIMP
SetFilePath(nsIFile * aFile)4210 nsMsgDBFolder::SetFilePath(nsIFile* aFile) {
4211   mPath = aFile;
4212   return NS_OK;
4213 }
4214 
4215 NS_IMETHODIMP
GetFilePath(nsIFile ** aFile)4216 nsMsgDBFolder::GetFilePath(nsIFile** aFile) {
4217   NS_ENSURE_ARG_POINTER(aFile);
4218   nsresult rv;
4219   // make a new nsIFile object in case the caller
4220   // alters the underlying file object.
4221   nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
4222   NS_ENSURE_SUCCESS(rv, rv);
4223   if (!mPath) parseURI(true);
4224   rv = file->InitWithFile(mPath);
4225   file.forget(aFile);
4226   return NS_OK;
4227 }
4228 
GetSummaryFile(nsIFile ** aSummaryFile)4229 NS_IMETHODIMP nsMsgDBFolder::GetSummaryFile(nsIFile** aSummaryFile) {
4230   NS_ENSURE_ARG_POINTER(aSummaryFile);
4231 
4232   nsresult rv;
4233   nsCOMPtr<nsIFile> newSummaryLocation =
4234       do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
4235   NS_ENSURE_SUCCESS(rv, rv);
4236 
4237   nsCOMPtr<nsIFile> pathFile;
4238   rv = GetFilePath(getter_AddRefs(pathFile));
4239   NS_ENSURE_SUCCESS(rv, rv);
4240 
4241   newSummaryLocation->InitWithFile(pathFile);
4242 
4243   nsString fileName;
4244   rv = newSummaryLocation->GetLeafName(fileName);
4245   NS_ENSURE_SUCCESS(rv, rv);
4246 
4247   fileName.AppendLiteral(SUMMARY_SUFFIX);
4248   rv = newSummaryLocation->SetLeafName(fileName);
4249   NS_ENSURE_SUCCESS(rv, rv);
4250 
4251   newSummaryLocation.forget(aSummaryFile);
4252   return NS_OK;
4253 }
4254 
4255 NS_IMETHODIMP
MarkMessagesRead(const nsTArray<RefPtr<nsIMsgDBHdr>> & messages,bool markRead)4256 nsMsgDBFolder::MarkMessagesRead(const nsTArray<RefPtr<nsIMsgDBHdr>>& messages,
4257                                 bool markRead) {
4258   for (auto message : messages) {
4259     nsresult rv = message->MarkRead(markRead);
4260     NS_ENSURE_SUCCESS(rv, rv);
4261   }
4262   return NS_OK;
4263 }
4264 
4265 NS_IMETHODIMP
MarkMessagesFlagged(const nsTArray<RefPtr<nsIMsgDBHdr>> & messages,bool markFlagged)4266 nsMsgDBFolder::MarkMessagesFlagged(
4267     const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, bool markFlagged) {
4268   for (auto message : messages) {
4269     nsresult rv = message->MarkFlagged(markFlagged);
4270     NS_ENSURE_SUCCESS(rv, rv);
4271   }
4272   return NS_OK;
4273 }
4274 
4275 NS_IMETHODIMP
SetLabelForMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aMessages,nsMsgLabelValue aLabel)4276 nsMsgDBFolder::SetLabelForMessages(
4277     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, nsMsgLabelValue aLabel) {
4278   GetDatabase();
4279   if (mDatabase) {
4280     for (auto message : aMessages) {
4281       nsMsgKey msgKey;
4282       (void)message->GetMessageKey(&msgKey);
4283       nsresult rv = mDatabase->SetLabel(msgKey, aLabel);
4284       NS_ENSURE_SUCCESS(rv, rv);
4285     }
4286   }
4287   return NS_OK;
4288 }
4289 
4290 NS_IMETHODIMP
SetJunkScoreForMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aMessages,const nsACString & junkScore)4291 nsMsgDBFolder::SetJunkScoreForMessages(
4292     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
4293     const nsACString& junkScore) {
4294   GetDatabase();
4295   if (mDatabase) {
4296     for (auto message : aMessages) {
4297       nsMsgKey msgKey;
4298       (void)message->GetMessageKey(&msgKey);
4299       mDatabase->SetStringProperty(msgKey, "junkscore",
4300                                    PromiseFlatCString(junkScore).get());
4301       mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter");
4302     }
4303   }
4304   return NS_OK;
4305 }
4306 
4307 NS_IMETHODIMP
ApplyRetentionSettings()4308 nsMsgDBFolder::ApplyRetentionSettings() { return ApplyRetentionSettings(true); }
4309 
ApplyRetentionSettings(bool deleteViaFolder)4310 nsresult nsMsgDBFolder::ApplyRetentionSettings(bool deleteViaFolder) {
4311   if (mFlags & nsMsgFolderFlags::Virtual)  // ignore virtual folders.
4312     return NS_OK;
4313   bool weOpenedDB = !mDatabase;
4314   nsCOMPtr<nsIMsgRetentionSettings> retentionSettings;
4315   nsresult rv = GetRetentionSettings(getter_AddRefs(retentionSettings));
4316   if (NS_SUCCEEDED(rv)) {
4317     nsMsgRetainByPreference retainByPreference =
4318         nsIMsgRetentionSettings::nsMsgRetainAll;
4319 
4320     retentionSettings->GetRetainByPreference(&retainByPreference);
4321     if (retainByPreference != nsIMsgRetentionSettings::nsMsgRetainAll) {
4322       rv = GetDatabase();
4323       NS_ENSURE_SUCCESS(rv, rv);
4324       if (mDatabase)
4325         rv = mDatabase->ApplyRetentionSettings(retentionSettings,
4326                                                deleteViaFolder);
4327     }
4328   }
4329   // we don't want applying retention settings to keep the db open, because
4330   // if we try to purge a bunch of folders, that will leave the dbs all open.
4331   // So if we opened the db, close it.
4332   if (weOpenedDB) CloseDBIfFolderNotOpen();
4333   return rv;
4334 }
4335 
4336 NS_IMETHODIMP
DeleteMessages(nsTArray<RefPtr<nsIMsgDBHdr>> const & messages,nsIMsgWindow * msgWindow,bool deleteStorage,bool isMove,nsIMsgCopyServiceListener * listener,bool allowUndo)4337 nsMsgDBFolder::DeleteMessages(nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
4338                               nsIMsgWindow* msgWindow, bool deleteStorage,
4339                               bool isMove, nsIMsgCopyServiceListener* listener,
4340                               bool allowUndo) {
4341   return NS_ERROR_NOT_IMPLEMENTED;
4342 }
4343 
4344 NS_IMETHODIMP
CopyMessages(nsIMsgFolder * srcFolder,nsTArray<RefPtr<nsIMsgDBHdr>> const & messages,bool isMove,nsIMsgWindow * window,nsIMsgCopyServiceListener * listener,bool isFolder,bool allowUndo)4345 nsMsgDBFolder::CopyMessages(nsIMsgFolder* srcFolder,
4346                             nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
4347                             bool isMove, nsIMsgWindow* window,
4348                             nsIMsgCopyServiceListener* listener, bool isFolder,
4349                             bool allowUndo) {
4350   return NS_ERROR_NOT_IMPLEMENTED;
4351 }
4352 
4353 NS_IMETHODIMP
CopyFolder(nsIMsgFolder * srcFolder,bool isMoveFolder,nsIMsgWindow * window,nsIMsgCopyServiceListener * listener)4354 nsMsgDBFolder::CopyFolder(nsIMsgFolder* srcFolder, bool isMoveFolder,
4355                           nsIMsgWindow* window,
4356                           nsIMsgCopyServiceListener* listener) {
4357   NS_ASSERTION(false, "should be overridden by child class");
4358   return NS_ERROR_NOT_IMPLEMENTED;
4359 }
4360 
4361 NS_IMETHODIMP
CopyFileMessage(nsIFile * aFile,nsIMsgDBHdr * messageToReplace,bool isDraftOrTemplate,uint32_t aNewMsgFlags,const nsACString & aNewMsgKeywords,nsIMsgWindow * window,nsIMsgCopyServiceListener * listener)4362 nsMsgDBFolder::CopyFileMessage(nsIFile* aFile, nsIMsgDBHdr* messageToReplace,
4363                                bool isDraftOrTemplate, uint32_t aNewMsgFlags,
4364                                const nsACString& aNewMsgKeywords,
4365                                nsIMsgWindow* window,
4366                                nsIMsgCopyServiceListener* listener) {
4367   return NS_ERROR_NOT_IMPLEMENTED;
4368 }
4369 
CopyDataToOutputStreamForAppend(nsIInputStream * aInStream,int32_t aLength,nsIOutputStream * aOutputStream)4370 NS_IMETHODIMP nsMsgDBFolder::CopyDataToOutputStreamForAppend(
4371     nsIInputStream* aInStream, int32_t aLength,
4372     nsIOutputStream* aOutputStream) {
4373   if (!aInStream) return NS_OK;
4374 
4375   uint32_t uiWritten;
4376   return aOutputStream->WriteFrom(aInStream, aLength, &uiWritten);
4377 }
4378 
CopyDataDone()4379 NS_IMETHODIMP nsMsgDBFolder::CopyDataDone() { return NS_OK; }
4380 
4381 #define NOTIFY_LISTENERS(propertyfunc_, params_)                       \
4382   PR_BEGIN_MACRO                                                       \
4383   nsTObserverArray<nsCOMPtr<nsIFolderListener>>::ForwardIterator iter( \
4384       mListeners);                                                     \
4385   nsCOMPtr<nsIFolderListener> listener;                                \
4386   while (iter.HasMore()) {                                             \
4387     listener = iter.GetNext();                                         \
4388     listener->propertyfunc_ params_;                                   \
4389   }                                                                    \
4390   PR_END_MACRO
4391 
4392 NS_IMETHODIMP
NotifyPropertyChanged(const nsACString & aProperty,const nsACString & aOldValue,const nsACString & aNewValue)4393 nsMsgDBFolder::NotifyPropertyChanged(const nsACString& aProperty,
4394                                      const nsACString& aOldValue,
4395                                      const nsACString& aNewValue) {
4396   NOTIFY_LISTENERS(OnItemPropertyChanged,
4397                    (this, aProperty, aOldValue, aNewValue));
4398 
4399   // Notify listeners who listen to every folder
4400   nsresult rv;
4401   nsCOMPtr<nsIFolderListener> folderListenerManager =
4402       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4403   NS_ENSURE_SUCCESS(rv, rv);
4404   return folderListenerManager->OnItemPropertyChanged(this, aProperty,
4405                                                       aOldValue, aNewValue);
4406 }
4407 
4408 NS_IMETHODIMP
NotifyUnicharPropertyChanged(const nsACString & aProperty,const nsAString & aOldValue,const nsAString & aNewValue)4409 nsMsgDBFolder::NotifyUnicharPropertyChanged(const nsACString& aProperty,
4410                                             const nsAString& aOldValue,
4411                                             const nsAString& aNewValue) {
4412   NOTIFY_LISTENERS(OnItemUnicharPropertyChanged,
4413                    (this, aProperty, aOldValue, aNewValue));
4414 
4415   // Notify listeners who listen to every folder
4416   nsresult rv;
4417   nsCOMPtr<nsIFolderListener> folderListenerManager =
4418       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4419   NS_ENSURE_SUCCESS(rv, rv);
4420   return folderListenerManager->OnItemUnicharPropertyChanged(
4421       this, aProperty, aOldValue, aNewValue);
4422 }
4423 
4424 NS_IMETHODIMP
NotifyIntPropertyChanged(const nsACString & aProperty,int64_t aOldValue,int64_t aNewValue)4425 nsMsgDBFolder::NotifyIntPropertyChanged(const nsACString& aProperty,
4426                                         int64_t aOldValue, int64_t aNewValue) {
4427   // Don't send off count notifications if they are turned off.
4428   if (!mNotifyCountChanges && (aProperty.Equals(kTotalMessages) ||
4429                                aProperty.Equals(kTotalUnreadMessages)))
4430     return NS_OK;
4431 
4432   NOTIFY_LISTENERS(OnItemIntPropertyChanged,
4433                    (this, aProperty, aOldValue, aNewValue));
4434 
4435   // Notify listeners who listen to every folder
4436   nsresult rv;
4437   nsCOMPtr<nsIFolderListener> folderListenerManager =
4438       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4439   NS_ENSURE_SUCCESS(rv, rv);
4440   return folderListenerManager->OnItemIntPropertyChanged(this, aProperty,
4441                                                          aOldValue, aNewValue);
4442 }
4443 
4444 NS_IMETHODIMP
NotifyBoolPropertyChanged(const nsACString & aProperty,bool aOldValue,bool aNewValue)4445 nsMsgDBFolder::NotifyBoolPropertyChanged(const nsACString& aProperty,
4446                                          bool aOldValue, bool aNewValue) {
4447   NOTIFY_LISTENERS(OnItemBoolPropertyChanged,
4448                    (this, aProperty, aOldValue, aNewValue));
4449 
4450   // Notify listeners who listen to every folder
4451   nsresult rv;
4452   nsCOMPtr<nsIFolderListener> folderListenerManager =
4453       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4454   NS_ENSURE_SUCCESS(rv, rv);
4455   return folderListenerManager->OnItemBoolPropertyChanged(this, aProperty,
4456                                                           aOldValue, aNewValue);
4457 }
4458 
4459 NS_IMETHODIMP
NotifyPropertyFlagChanged(nsIMsgDBHdr * aItem,const nsACString & aProperty,uint32_t aOldValue,uint32_t aNewValue)4460 nsMsgDBFolder::NotifyPropertyFlagChanged(nsIMsgDBHdr* aItem,
4461                                          const nsACString& aProperty,
4462                                          uint32_t aOldValue,
4463                                          uint32_t aNewValue) {
4464   NOTIFY_LISTENERS(OnItemPropertyFlagChanged,
4465                    (aItem, aProperty, aOldValue, aNewValue));
4466 
4467   // Notify listeners who listen to every folder
4468   nsresult rv;
4469   nsCOMPtr<nsIFolderListener> folderListenerManager =
4470       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4471   NS_ENSURE_SUCCESS(rv, rv);
4472   return folderListenerManager->OnItemPropertyFlagChanged(aItem, aProperty,
4473                                                           aOldValue, aNewValue);
4474 }
4475 
NotifyItemAdded(nsISupports * aItem)4476 NS_IMETHODIMP nsMsgDBFolder::NotifyItemAdded(nsISupports* aItem) {
4477   static bool notify = true;
4478 
4479   if (!notify) return NS_OK;
4480 
4481   NOTIFY_LISTENERS(OnItemAdded, (this, aItem));
4482 
4483   // Notify listeners who listen to every folder
4484   nsresult rv;
4485   nsCOMPtr<nsIFolderListener> folderListenerManager =
4486       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4487   NS_ENSURE_SUCCESS(rv, rv);
4488   return folderListenerManager->OnItemAdded(this, aItem);
4489 }
4490 
NotifyItemRemoved(nsISupports * aItem)4491 nsresult nsMsgDBFolder::NotifyItemRemoved(nsISupports* aItem) {
4492   NOTIFY_LISTENERS(OnItemRemoved, (this, aItem));
4493 
4494   // Notify listeners who listen to every folder
4495   nsresult rv;
4496   nsCOMPtr<nsIFolderListener> folderListenerManager =
4497       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4498   NS_ENSURE_SUCCESS(rv, rv);
4499   return folderListenerManager->OnItemRemoved(this, aItem);
4500 }
4501 
NotifyFolderEvent(const nsACString & aEvent)4502 nsresult nsMsgDBFolder::NotifyFolderEvent(const nsACString& aEvent) {
4503   NOTIFY_LISTENERS(OnItemEvent, (this, aEvent));
4504 
4505   // Notify listeners who listen to every folder
4506   nsresult rv;
4507   nsCOMPtr<nsIFolderListener> folderListenerManager =
4508       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4509   NS_ENSURE_SUCCESS(rv, rv);
4510   return folderListenerManager->OnItemEvent(this, aEvent);
4511 }
4512 
4513 NS_IMETHODIMP
GetFilterList(nsIMsgWindow * aMsgWindow,nsIMsgFilterList ** aResult)4514 nsMsgDBFolder::GetFilterList(nsIMsgWindow* aMsgWindow,
4515                              nsIMsgFilterList** aResult) {
4516   nsCOMPtr<nsIMsgIncomingServer> server;
4517   nsresult rv = GetServer(getter_AddRefs(server));
4518   NS_ENSURE_SUCCESS(rv, rv);
4519   return server->GetFilterList(aMsgWindow, aResult);
4520 }
4521 
4522 NS_IMETHODIMP
SetFilterList(nsIMsgFilterList * aFilterList)4523 nsMsgDBFolder::SetFilterList(nsIMsgFilterList* aFilterList) {
4524   nsCOMPtr<nsIMsgIncomingServer> server;
4525   nsresult rv = GetServer(getter_AddRefs(server));
4526   NS_ENSURE_SUCCESS(rv, rv);
4527   return server->SetFilterList(aFilterList);
4528 }
4529 
4530 NS_IMETHODIMP
GetEditableFilterList(nsIMsgWindow * aMsgWindow,nsIMsgFilterList ** aResult)4531 nsMsgDBFolder::GetEditableFilterList(nsIMsgWindow* aMsgWindow,
4532                                      nsIMsgFilterList** aResult) {
4533   NS_ENSURE_ARG_POINTER(aResult);
4534   nsCOMPtr<nsIMsgIncomingServer> server;
4535   nsresult rv = GetServer(getter_AddRefs(server));
4536   NS_ENSURE_SUCCESS(rv, rv);
4537   return server->GetEditableFilterList(aMsgWindow, aResult);
4538 }
4539 
4540 NS_IMETHODIMP
SetEditableFilterList(nsIMsgFilterList * aFilterList)4541 nsMsgDBFolder::SetEditableFilterList(nsIMsgFilterList* aFilterList) {
4542   nsCOMPtr<nsIMsgIncomingServer> server;
4543   nsresult rv = GetServer(getter_AddRefs(server));
4544   NS_ENSURE_SUCCESS(rv, rv);
4545   return server->SetEditableFilterList(aFilterList);
4546 }
4547 
4548 /* void enableNotifications (in long notificationType, in boolean enable); */
EnableNotifications(int32_t notificationType,bool enable)4549 NS_IMETHODIMP nsMsgDBFolder::EnableNotifications(int32_t notificationType,
4550                                                  bool enable) {
4551   if (notificationType == nsIMsgFolder::allMessageCountNotifications) {
4552     mNotifyCountChanges = enable;
4553     if (enable) {
4554       UpdateSummaryTotals(true);
4555     }
4556     return NS_OK;
4557   }
4558   return NS_ERROR_NOT_IMPLEMENTED;
4559 }
4560 
GetMessageHeader(nsMsgKey msgKey,nsIMsgDBHdr ** aMsgHdr)4561 NS_IMETHODIMP nsMsgDBFolder::GetMessageHeader(nsMsgKey msgKey,
4562                                               nsIMsgDBHdr** aMsgHdr) {
4563   NS_ENSURE_ARG_POINTER(aMsgHdr);
4564   nsCOMPtr<nsIMsgDatabase> database;
4565   nsresult rv = GetMsgDatabase(getter_AddRefs(database));
4566   NS_ENSURE_SUCCESS(rv, rv);
4567   return (database) ? database->GetMsgHdrForKey(msgKey, aMsgHdr)
4568                     : NS_ERROR_FAILURE;
4569 }
4570 
GetDescendants(nsTArray<RefPtr<nsIMsgFolder>> & aDescendants)4571 NS_IMETHODIMP nsMsgDBFolder::GetDescendants(
4572     nsTArray<RefPtr<nsIMsgFolder>>& aDescendants) {
4573   aDescendants.Clear();
4574   for (nsIMsgFolder* child : mSubFolders) {
4575     aDescendants.AppendElement(child);
4576     nsTArray<RefPtr<nsIMsgFolder>> grandchildren;
4577     child->GetDescendants(grandchildren);
4578     aDescendants.AppendElements(grandchildren);
4579   }
4580   return NS_OK;
4581 }
4582 
GetBaseMessageURI(nsACString & baseMessageURI)4583 NS_IMETHODIMP nsMsgDBFolder::GetBaseMessageURI(nsACString& baseMessageURI) {
4584   if (mBaseMessageURI.IsEmpty()) return NS_ERROR_FAILURE;
4585   baseMessageURI = mBaseMessageURI;
4586   return NS_OK;
4587 }
4588 
GetUriForMsg(nsIMsgDBHdr * msgHdr,nsACString & aURI)4589 NS_IMETHODIMP nsMsgDBFolder::GetUriForMsg(nsIMsgDBHdr* msgHdr,
4590                                           nsACString& aURI) {
4591   NS_ENSURE_ARG(msgHdr);
4592   nsMsgKey msgKey;
4593   msgHdr->GetMessageKey(&msgKey);
4594   nsAutoCString uri;
4595   uri.Assign(mBaseMessageURI);
4596 
4597   // append a "#" followed by the message key.
4598   uri.Append('#');
4599   uri.AppendInt(msgKey);
4600   aURI = uri;
4601   return NS_OK;
4602 }
4603 
GenerateMessageURI(nsMsgKey msgKey,nsACString & aURI)4604 NS_IMETHODIMP nsMsgDBFolder::GenerateMessageURI(nsMsgKey msgKey,
4605                                                 nsACString& aURI) {
4606   nsCString uri;
4607   nsresult rv = GetBaseMessageURI(uri);
4608   NS_ENSURE_SUCCESS(rv, rv);
4609 
4610   // append a "#" followed by the message key.
4611   uri.Append('#');
4612   uri.AppendInt(msgKey);
4613   aURI = uri;
4614   return NS_OK;
4615 }
4616 
GetBaseStringBundle(nsIStringBundle ** aBundle)4617 nsresult nsMsgDBFolder::GetBaseStringBundle(nsIStringBundle** aBundle) {
4618   NS_ENSURE_ARG_POINTER(aBundle);
4619   nsCOMPtr<nsIStringBundleService> bundleService =
4620       mozilla::services::GetStringBundleService();
4621   NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
4622   nsCOMPtr<nsIStringBundle> bundle;
4623   bundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
4624                               getter_AddRefs(bundle));
4625   bundle.forget(aBundle);
4626   return NS_OK;
4627 }
4628 
4629 // Do not use this routine if you have to call it very often because
4630 // it creates a new bundle each time
GetStringFromBundle(const char * msgName,nsString & aResult)4631 nsresult nsMsgDBFolder::GetStringFromBundle(const char* msgName,
4632                                             nsString& aResult) {
4633   nsresult rv;
4634   nsCOMPtr<nsIStringBundle> bundle;
4635   rv = GetBaseStringBundle(getter_AddRefs(bundle));
4636   if (NS_SUCCEEDED(rv) && bundle)
4637     rv = bundle->GetStringFromName(msgName, aResult);
4638   return rv;
4639 }
4640 
ThrowConfirmationPrompt(nsIMsgWindow * msgWindow,const nsAString & confirmString,bool * confirmed)4641 nsresult nsMsgDBFolder::ThrowConfirmationPrompt(nsIMsgWindow* msgWindow,
4642                                                 const nsAString& confirmString,
4643                                                 bool* confirmed) {
4644   if (msgWindow) {
4645     nsCOMPtr<nsIDocShell> docShell;
4646     msgWindow->GetRootDocShell(getter_AddRefs(docShell));
4647     if (docShell) {
4648       nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
4649       if (dialog && !confirmString.IsEmpty())
4650         dialog->Confirm(nullptr, nsString(confirmString).get(), confirmed);
4651     }
4652   }
4653   return NS_OK;
4654 }
4655 
4656 NS_IMETHODIMP
GetStringWithFolderNameFromBundle(const char * msgName,nsAString & aResult)4657 nsMsgDBFolder::GetStringWithFolderNameFromBundle(const char* msgName,
4658                                                  nsAString& aResult) {
4659   nsCOMPtr<nsIStringBundle> bundle;
4660   nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
4661   if (NS_SUCCEEDED(rv) && bundle) {
4662     nsString folderName;
4663     GetName(folderName);
4664     AutoTArray<nsString, 2> formatStrings = {folderName,
4665                                              kLocalizedBrandShortName};
4666     nsString resultStr;
4667     rv = bundle->FormatStringFromName(msgName, formatStrings, resultStr);
4668     if (NS_SUCCEEDED(rv)) aResult.Assign(resultStr);
4669   }
4670   return rv;
4671 }
4672 
ConfirmFolderDeletionForFilter(nsIMsgWindow * msgWindow,bool * confirmed)4673 NS_IMETHODIMP nsMsgDBFolder::ConfirmFolderDeletionForFilter(
4674     nsIMsgWindow* msgWindow, bool* confirmed) {
4675   nsString confirmString;
4676   nsresult rv = GetStringWithFolderNameFromBundle(
4677       "confirmFolderDeletionForFilter", confirmString);
4678   NS_ENSURE_SUCCESS(rv, rv);
4679   return ThrowConfirmationPrompt(msgWindow, confirmString, confirmed);
4680 }
4681 
ThrowAlertMsg(const char * msgName,nsIMsgWindow * msgWindow)4682 NS_IMETHODIMP nsMsgDBFolder::ThrowAlertMsg(const char* msgName,
4683                                            nsIMsgWindow* msgWindow) {
4684   if (!msgWindow) {
4685     return NS_OK;
4686   }
4687 
4688   nsCOMPtr<nsIStringBundle> bundle;
4689   nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
4690   NS_ENSURE_SUCCESS(rv, rv);
4691 
4692   // Assemble a pretty folder identifier, e.g. "Trash on bob@example.com".
4693   nsAutoString ident;
4694   nsAutoString folderName;
4695   GetName(folderName);
4696   nsAutoString serverName;
4697   nsCOMPtr<nsIMsgIncomingServer> server;
4698   if (NS_SUCCEEDED(GetServer(getter_AddRefs(server)))) {
4699     server->GetPrettyName(serverName);
4700     bundle->FormatStringFromName("verboseFolderFormat",
4701                                  {folderName, serverName}, ident);
4702   }
4703   if (ident.IsEmpty()) {
4704     ident = folderName;  // Fallback, just in case.
4705   }
4706 
4707   // Format the actual error message (NOTE: not all error messages use the
4708   // params - extra values are just ignored).
4709   nsAutoString alertString;
4710   rv = bundle->FormatStringFromName(msgName, {ident, kLocalizedBrandShortName},
4711                                     alertString);
4712   NS_ENSURE_SUCCESS(rv, rv);
4713 
4714   // Include the folder identifier in the alert title for good measure,
4715   // because not all the error messages include the folder.
4716   nsAutoString title;
4717   bundle->FormatStringFromName("folderErrorAlertTitle", {ident}, title);
4718 
4719   nsCOMPtr<nsIPrompt> dialog;
4720   rv = msgWindow->GetPromptDialog(getter_AddRefs(dialog));
4721   NS_ENSURE_SUCCESS(rv, rv);
4722 
4723   return dialog->Alert(title.IsEmpty() ? nullptr : title.get(),
4724                        alertString.get());
4725 }
4726 
AlertFilterChanged(nsIMsgWindow * msgWindow)4727 NS_IMETHODIMP nsMsgDBFolder::AlertFilterChanged(nsIMsgWindow* msgWindow) {
4728   NS_ENSURE_ARG(msgWindow);
4729   nsresult rv = NS_OK;
4730   bool checkBox = false;
4731   GetWarnFilterChanged(&checkBox);
4732   if (!checkBox) {
4733     nsCOMPtr<nsIDocShell> docShell;
4734     msgWindow->GetRootDocShell(getter_AddRefs(docShell));
4735     nsString alertString;
4736     rv = GetStringFromBundle("alertFilterChanged", alertString);
4737     nsString alertCheckbox;
4738     rv = GetStringFromBundle("alertFilterCheckbox", alertCheckbox);
4739     if (!alertString.IsEmpty() && !alertCheckbox.IsEmpty() && docShell) {
4740       nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
4741       if (dialog) {
4742         dialog->AlertCheck(nullptr, alertString.get(), alertCheckbox.get(),
4743                            &checkBox);
4744         SetWarnFilterChanged(checkBox);
4745       }
4746     }
4747   }
4748   return rv;
4749 }
4750 
GetWarnFilterChanged(bool * aVal)4751 nsresult nsMsgDBFolder::GetWarnFilterChanged(bool* aVal) {
4752   NS_ENSURE_ARG(aVal);
4753   nsresult rv;
4754   nsCOMPtr<nsIPrefBranch> prefBranch =
4755       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
4756   NS_ENSURE_SUCCESS(rv, rv);
4757   rv = prefBranch->GetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal);
4758   if (NS_FAILED(rv)) *aVal = false;
4759   return NS_OK;
4760 }
4761 
SetWarnFilterChanged(bool aVal)4762 nsresult nsMsgDBFolder::SetWarnFilterChanged(bool aVal) {
4763   nsresult rv = NS_OK;
4764   nsCOMPtr<nsIPrefBranch> prefBranch =
4765       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
4766   NS_ENSURE_SUCCESS(rv, rv);
4767   return prefBranch->SetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal);
4768 }
4769 
NotifyCompactCompleted()4770 NS_IMETHODIMP nsMsgDBFolder::NotifyCompactCompleted() {
4771   NS_ASSERTION(false, "should be overridden by child class");
4772   return NS_ERROR_NOT_IMPLEMENTED;
4773 }
4774 
CloseDBIfFolderNotOpen()4775 nsresult nsMsgDBFolder::CloseDBIfFolderNotOpen() {
4776   nsresult rv;
4777   nsCOMPtr<nsIMsgMailSession> session =
4778       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4779   NS_ENSURE_SUCCESS(rv, rv);
4780   bool folderOpen;
4781   session->IsFolderOpenInWindow(this, &folderOpen);
4782   if (!folderOpen &&
4783       !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
4784     SetMsgDatabase(nullptr);
4785   return NS_OK;
4786 }
4787 
SetSortOrder(int32_t order)4788 NS_IMETHODIMP nsMsgDBFolder::SetSortOrder(int32_t order) {
4789   NS_ASSERTION(false, "not implemented");
4790   return NS_ERROR_NOT_IMPLEMENTED;
4791 }
4792 
GetSortOrder(int32_t * order)4793 NS_IMETHODIMP nsMsgDBFolder::GetSortOrder(int32_t* order) {
4794   NS_ENSURE_ARG_POINTER(order);
4795 
4796   uint32_t flags;
4797   nsresult rv = GetFlags(&flags);
4798   NS_ENSURE_SUCCESS(rv, rv);
4799 
4800   if (flags & nsMsgFolderFlags::Inbox)
4801     *order = 0;
4802   else if (flags & nsMsgFolderFlags::Drafts)
4803     *order = 1;
4804   else if (flags & nsMsgFolderFlags::Templates)
4805     *order = 2;
4806   else if (flags & nsMsgFolderFlags::SentMail)
4807     *order = 3;
4808   else if (flags & nsMsgFolderFlags::Archive)
4809     *order = 4;
4810   else if (flags & nsMsgFolderFlags::Junk)
4811     *order = 5;
4812   else if (flags & nsMsgFolderFlags::Trash)
4813     *order = 6;
4814   else if (flags & nsMsgFolderFlags::Virtual)
4815     *order = 7;
4816   else if (flags & nsMsgFolderFlags::Queue)
4817     *order = 8;
4818   else
4819     *order = 9;
4820 
4821   return NS_OK;
4822 }
4823 
4824 // static Helper function for CompareSortKeys().
4825 // Builds a collation key for a given folder based on "{sortOrder}{name}"
BuildFolderSortKey(nsIMsgFolder * aFolder,nsTArray<uint8_t> & aKey)4826 nsresult nsMsgDBFolder::BuildFolderSortKey(nsIMsgFolder* aFolder,
4827                                            nsTArray<uint8_t>& aKey) {
4828   aKey.Clear();
4829   int32_t order;
4830   nsresult rv = aFolder->GetSortOrder(&order);
4831   NS_ENSURE_SUCCESS(rv, rv);
4832   nsAutoString orderString;
4833   orderString.AppendInt(order);
4834   nsString folderName;
4835   rv = aFolder->GetName(folderName);
4836   NS_ENSURE_SUCCESS(rv, rv);
4837   orderString.Append(folderName);
4838   NS_ENSURE_TRUE(gCollationKeyGenerator, NS_ERROR_NULL_POINTER);
4839   return gCollationKeyGenerator->AllocateRawSortKey(
4840       nsICollation::kCollationCaseInSensitive, orderString, aKey);
4841 }
4842 
CompareSortKeys(nsIMsgFolder * aFolder,int32_t * sortOrder)4843 NS_IMETHODIMP nsMsgDBFolder::CompareSortKeys(nsIMsgFolder* aFolder,
4844                                              int32_t* sortOrder) {
4845   nsTArray<uint8_t> sortKey1;
4846   nsTArray<uint8_t> sortKey2;
4847   nsresult rv = BuildFolderSortKey(this, sortKey1);
4848   NS_ENSURE_SUCCESS(rv, rv);
4849   rv = BuildFolderSortKey(aFolder, sortKey2);
4850   NS_ENSURE_SUCCESS(rv, rv);
4851   rv = gCollationKeyGenerator->CompareRawSortKey(sortKey1, sortKey2, sortOrder);
4852   return rv;
4853 }
4854 
FetchMsgPreviewText(nsTArray<nsMsgKey> const & aKeysToFetch,bool aLocalOnly,nsIUrlListener * aUrlListener,bool * aAsyncResults)4855 NS_IMETHODIMP nsMsgDBFolder::FetchMsgPreviewText(
4856     nsTArray<nsMsgKey> const& aKeysToFetch, bool aLocalOnly,
4857     nsIUrlListener* aUrlListener, bool* aAsyncResults) {
4858   NS_ENSURE_ARG_POINTER(aAsyncResults);
4859   return NS_ERROR_NOT_IMPLEMENTED;
4860 }
4861 
GetMsgTextFromStream(nsIInputStream * stream,const nsACString & aCharset,uint32_t bytesToRead,uint32_t aMaxOutputLen,bool aCompressQuotes,bool aStripHTMLTags,nsACString & aContentType,nsACString & aMsgText)4862 NS_IMETHODIMP nsMsgDBFolder::GetMsgTextFromStream(
4863     nsIInputStream* stream, const nsACString& aCharset, uint32_t bytesToRead,
4864     uint32_t aMaxOutputLen, bool aCompressQuotes, bool aStripHTMLTags,
4865     nsACString& aContentType, nsACString& aMsgText) {
4866   /*
4867      1. non mime message - the message body starts after the blank line
4868         following the headers.
4869      2. mime message, multipart/alternative - we could simply scan for the
4870         boundary line, advance past its headers, and treat the next few lines
4871         as the text.
4872      3. mime message, text/plain - body follows headers
4873      4. multipart/mixed - scan past boundary, treat next part as body.
4874    */
4875 
4876   UniquePtr<nsLineBuffer<char>> lineBuffer(new nsLineBuffer<char>);
4877 
4878   nsAutoCString msgText;
4879   nsAutoString contentType;
4880   nsAutoString encoding;
4881   nsAutoCString curLine;
4882   nsAutoCString charset(aCharset);
4883 
4884   // might want to use a state var instead of bools.
4885   bool msgBodyIsHtml = false;
4886   bool more = true;
4887   bool reachedEndBody = false;
4888   bool isBase64 = false;
4889   bool inMsgBody = false;
4890   bool justPassedEndBoundary = false;
4891 
4892   uint32_t bytesRead = 0;
4893 
4894   nsresult rv;
4895 
4896   // Both are used to extract data from the headers
4897   nsCOMPtr<nsIMimeHeaders> mimeHeaders(
4898       do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv));
4899   NS_ENSURE_SUCCESS(rv, rv);
4900   nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParam(
4901       do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv));
4902   NS_ENSURE_SUCCESS(rv, rv);
4903 
4904   // Stack of boundaries, used to figure out where we are
4905   nsTArray<nsCString> boundaryStack;
4906 
4907   while (!inMsgBody && bytesRead <= bytesToRead) {
4908     nsAutoCString msgHeaders;
4909     // We want to NS_ReadLine until we get to a blank line (the end of the
4910     // headers)
4911     while (more) {
4912       rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more);
4913       NS_ENSURE_SUCCESS(rv, rv);
4914       if (curLine.IsEmpty()) break;
4915       msgHeaders.Append(curLine);
4916       msgHeaders.AppendLiteral("\r\n");
4917       bytesRead += curLine.Length();
4918       if (bytesRead > bytesToRead) break;
4919     }
4920 
4921     // There's no point in processing if we can't get the body
4922     if (bytesRead > bytesToRead) break;
4923 
4924     // Process the headers, looking for things we need
4925     rv = mimeHeaders->Initialize(msgHeaders);
4926     NS_ENSURE_SUCCESS(rv, rv);
4927 
4928     nsAutoCString contentTypeHdr;
4929     mimeHeaders->ExtractHeader("Content-Type", false, contentTypeHdr);
4930 
4931     // Get the content type
4932     // If we don't have a content type, then we assign text/plain
4933     // this is in violation of the RFC for multipart/digest, though
4934     // Also, if we've just passed an end boundary, we're going to ignore this.
4935     if (!justPassedEndBoundary && contentTypeHdr.IsEmpty())
4936       contentType.AssignLiteral(u"text/plain");
4937     else
4938       mimeHdrParam->GetParameter(contentTypeHdr, nullptr, EmptyCString(), false,
4939                                  nullptr, contentType);
4940 
4941     justPassedEndBoundary = false;
4942 
4943     // If we are multipart, then we need to get the boundary
4944     if (StringBeginsWith(contentType, u"multipart/"_ns,
4945                          nsCaseInsensitiveStringComparator)) {
4946       nsAutoString boundaryParam;
4947       mimeHdrParam->GetParameter(contentTypeHdr, "boundary", EmptyCString(),
4948                                  false, nullptr, boundaryParam);
4949       if (!boundaryParam.IsEmpty()) {
4950         nsAutoCString boundary("--"_ns);
4951         boundary.Append(NS_ConvertUTF16toUTF8(boundaryParam));
4952         boundaryStack.AppendElement(boundary);
4953       }
4954     }
4955 
4956     // If we are message/rfc822, then there's another header block coming up
4957     else if (contentType.LowerCaseEqualsLiteral("message/rfc822"))
4958       continue;
4959 
4960     // If we are a text part, then we want it
4961     else if (StringBeginsWith(contentType, u"text/"_ns,
4962                               nsCaseInsensitiveStringComparator)) {
4963       inMsgBody = true;
4964 
4965       if (contentType.LowerCaseEqualsLiteral("text/html")) msgBodyIsHtml = true;
4966 
4967       // Also get the charset if required
4968       if (charset.IsEmpty()) {
4969         nsAutoString charsetW;
4970         mimeHdrParam->GetParameter(contentTypeHdr, "charset", EmptyCString(),
4971                                    false, nullptr, charsetW);
4972         charset.Assign(NS_ConvertUTF16toUTF8(charsetW));
4973       }
4974 
4975       // Finally, get the encoding
4976       nsAutoCString encodingHdr;
4977       mimeHeaders->ExtractHeader("Content-Transfer-Encoding", false,
4978                                  encodingHdr);
4979       if (!encodingHdr.IsEmpty())
4980         mimeHdrParam->GetParameter(encodingHdr, nullptr, EmptyCString(), false,
4981                                    nullptr, encoding);
4982 
4983       if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) isBase64 = true;
4984     }
4985 
4986     // We need to consume the rest, until the next headers
4987     uint32_t count = boundaryStack.Length();
4988     nsAutoCString boundary;
4989     nsAutoCString endBoundary;
4990     if (count) {
4991       boundary.Assign(boundaryStack.ElementAt(count - 1));
4992       endBoundary.Assign(boundary);
4993       endBoundary.AppendLiteral("--");
4994     }
4995     while (more) {
4996       rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more);
4997       NS_ENSURE_SUCCESS(rv, rv);
4998 
4999       if (count) {
5000         // If we've reached a MIME final delimiter, pop and break
5001         if (StringBeginsWith(curLine, endBoundary)) {
5002           if (inMsgBody) reachedEndBody = true;
5003           boundaryStack.RemoveElementAt(count - 1);
5004           justPassedEndBoundary = true;
5005           break;
5006         }
5007         // If we've reached the end of this MIME part, we can break
5008         if (StringBeginsWith(curLine, boundary)) {
5009           if (inMsgBody) reachedEndBody = true;
5010           break;
5011         }
5012       }
5013 
5014       // Only append the text if we're actually in the message body
5015       if (inMsgBody) {
5016         msgText.Append(curLine);
5017         if (!isBase64) msgText.AppendLiteral("\r\n");
5018       }
5019 
5020       bytesRead += curLine.Length();
5021       if (bytesRead > bytesToRead) break;
5022     }
5023   }
5024   lineBuffer.reset();
5025 
5026   // if the snippet is encoded, decode it
5027   if (!encoding.IsEmpty())
5028     decodeMsgSnippet(NS_ConvertUTF16toUTF8(encoding), !reachedEndBody, msgText);
5029 
5030   // In order to turn our snippet into unicode, we need to convert it from the
5031   // charset we detected earlier.
5032   nsString unicodeMsgBodyStr;
5033   nsMsgI18NConvertToUnicode(charset, msgText, unicodeMsgBodyStr);
5034 
5035   // now we've got a msg body. If it's html, convert it to plain text.
5036   if (msgBodyIsHtml && aStripHTMLTags)
5037     ConvertMsgSnippetToPlainText(unicodeMsgBodyStr, unicodeMsgBodyStr);
5038 
5039   // We want to remove any whitespace from the beginning and end of the string
5040   unicodeMsgBodyStr.Trim(" \t\r\n", true, true);
5041 
5042   // step 3, optionally remove quoted text from the snippet
5043   nsString compressedQuotesMsgStr;
5044   if (aCompressQuotes)
5045     compressQuotesInMsgSnippet(unicodeMsgBodyStr, compressedQuotesMsgStr);
5046 
5047   // now convert back to utf-8 which is more convenient for storage
5048   CopyUTF16toUTF8(aCompressQuotes ? compressedQuotesMsgStr : unicodeMsgBodyStr,
5049                   aMsgText);
5050 
5051   // finally, truncate the string based on aMaxOutputLen
5052   if (aMsgText.Length() > aMaxOutputLen) {
5053     if (NS_IsAscii(aMsgText.BeginReading()))
5054       aMsgText.SetLength(aMaxOutputLen);
5055     else
5056       nsMsgI18NShrinkUTF8Str(nsCString(aMsgText), aMaxOutputLen, aMsgText);
5057   }
5058 
5059   // Also assign the content type being returned
5060   aContentType.Assign(NS_ConvertUTF16toUTF8(contentType));
5061   return rv;
5062 }
5063 
5064 /**
5065  * decodeMsgSnippet - helper function which applies the appropriate transfer
5066  * decoding to the message snippet based on aEncodingType. Currently handles
5067  * base64 and quoted-printable. If aEncodingType refers to an encoding we
5068  * don't handle, the message data is passed back unmodified.
5069  * @param aEncodingType  the encoding type (base64, quoted-printable)
5070  * @param aIsComplete    the snippet is actually the entire message so the
5071  *                       decoder doesn't have to worry about partial data
5072  * @param aMsgSnippet in/out argument. The encoded msg snippet and then the
5073  *                                     decoded snippet
5074  */
decodeMsgSnippet(const nsACString & aEncodingType,bool aIsComplete,nsCString & aMsgSnippet)5075 void nsMsgDBFolder::decodeMsgSnippet(const nsACString& aEncodingType,
5076                                      bool aIsComplete, nsCString& aMsgSnippet) {
5077   if (aEncodingType.LowerCaseEqualsLiteral(ENCODING_BASE64)) {
5078     int32_t base64Len = aMsgSnippet.Length();
5079     if (aIsComplete) base64Len -= base64Len % 4;
5080     char* decodedBody = PL_Base64Decode(aMsgSnippet.get(), base64Len, nullptr);
5081     if (decodedBody) aMsgSnippet.Adopt(decodedBody);
5082   } else if (aEncodingType.LowerCaseEqualsLiteral(ENCODING_QUOTED_PRINTABLE)) {
5083     MsgStripQuotedPrintable(aMsgSnippet);
5084   }
5085 }
5086 
5087 /**
5088  * stripQuotesFromMsgSnippet - Reduces quoted reply text including the citation
5089  * (Scott wrote:) from the message snippet to " ... ". Assumes the snippet has
5090  * been decoded and converted to plain text.
5091  * @param aMsgSnippet in/out argument. The string to strip quotes from.
5092  */
compressQuotesInMsgSnippet(const nsString & aMsgSnippet,nsAString & aCompressedQuotes)5093 void nsMsgDBFolder::compressQuotesInMsgSnippet(const nsString& aMsgSnippet,
5094                                                nsAString& aCompressedQuotes) {
5095   int32_t msgBodyStrLen = aMsgSnippet.Length();
5096   bool lastLineWasAQuote = false;
5097   int32_t offset = 0;
5098   int32_t lineFeedPos = 0;
5099   while (offset < msgBodyStrLen) {
5100     lineFeedPos = aMsgSnippet.FindChar('\n', offset);
5101     if (lineFeedPos != -1) {
5102       const nsAString& currentLine =
5103           Substring(aMsgSnippet, offset, lineFeedPos - offset);
5104       // this catches quoted text ("> "), nested quotes of any level (">> ",
5105       // ">>> ", ...) it also catches empty line quoted text (">"). It might be
5106       // over aggressive and require tweaking later. Try to strip the citation.
5107       // If the current line ends with a ':' and the next line looks like a
5108       // quoted reply (starts with a ">") skip the current line
5109       if (StringBeginsWith(currentLine, u">"_ns) ||
5110           (lineFeedPos + 1 < msgBodyStrLen && lineFeedPos &&
5111            aMsgSnippet[lineFeedPos - 1] == char16_t(':') &&
5112            aMsgSnippet[lineFeedPos + 1] == char16_t('>'))) {
5113         lastLineWasAQuote = true;
5114       } else if (!currentLine.IsEmpty()) {
5115         if (lastLineWasAQuote) {
5116           aCompressedQuotes += u" ... "_ns;
5117           lastLineWasAQuote = false;
5118         }
5119 
5120         aCompressedQuotes += currentLine;
5121         // Don't forget to substitute a space for the line feed.
5122         aCompressedQuotes += char16_t(' ');
5123       }
5124 
5125       offset = lineFeedPos + 1;
5126     } else {
5127       aCompressedQuotes.Append(
5128           Substring(aMsgSnippet, offset, msgBodyStrLen - offset));
5129       break;
5130     }
5131   }
5132 }
5133 
ConvertMsgSnippetToPlainText(const nsAString & aMessageText,nsAString & aOutText)5134 NS_IMETHODIMP nsMsgDBFolder::ConvertMsgSnippetToPlainText(
5135     const nsAString& aMessageText, nsAString& aOutText) {
5136   uint32_t flags = nsIDocumentEncoder::OutputLFLineBreak |
5137                    nsIDocumentEncoder::OutputNoScriptContent |
5138                    nsIDocumentEncoder::OutputNoFramesContent |
5139                    nsIDocumentEncoder::OutputBodyOnly;
5140   nsCOMPtr<nsIParserUtils> utils = do_GetService(NS_PARSERUTILS_CONTRACTID);
5141   return utils->ConvertToPlainText(aMessageText, flags, 80, aOutText);
5142 }
5143 
GetMsgPreviewTextFromStream(nsIMsgDBHdr * msgHdr,nsIInputStream * stream)5144 nsresult nsMsgDBFolder::GetMsgPreviewTextFromStream(nsIMsgDBHdr* msgHdr,
5145                                                     nsIInputStream* stream) {
5146   nsCString msgBody;
5147   nsAutoCString charset;
5148   msgHdr->GetCharset(getter_Copies(charset));
5149   nsAutoCString contentType;
5150   nsresult rv = GetMsgTextFromStream(stream, charset, 4096, 255, true, true,
5151                                      contentType, msgBody);
5152   // replaces all tabs and line returns with a space,
5153   // then trims off leading and trailing white space
5154   msgBody.CompressWhitespace();
5155   msgHdr->SetStringProperty("preview", msgBody.get());
5156   return rv;
5157 }
5158 
UpdateTimestamps(bool allowUndo)5159 void nsMsgDBFolder::UpdateTimestamps(bool allowUndo) {
5160   if (!(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
5161     SetMRUTime();
5162     if (allowUndo)  // This is a proxy for a user-initiated act.
5163     {
5164       bool isArchive;
5165       IsSpecialFolder(nsMsgFolderFlags::Archive, true, &isArchive);
5166       if (!isArchive) {
5167         SetMRMTime();
5168         NotifyFolderEvent(kMRMTimeChanged);
5169       }
5170     }
5171   }
5172 }
5173 
SetMRUTime()5174 void nsMsgDBFolder::SetMRUTime() {
5175   uint32_t seconds;
5176   PRTime2Seconds(PR_Now(), &seconds);
5177   nsAutoCString nowStr;
5178   nowStr.AppendInt(seconds);
5179   SetStringProperty(MRU_TIME_PROPERTY, nowStr);
5180 }
5181 
SetMRMTime()5182 void nsMsgDBFolder::SetMRMTime() {
5183   uint32_t seconds;
5184   PRTime2Seconds(PR_Now(), &seconds);
5185   nsAutoCString nowStr;
5186   nowStr.AppendInt(seconds);
5187   SetStringProperty(MRM_TIME_PROPERTY, nowStr);
5188 }
5189 
AddKeywordsToMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aMessages,const nsACString & aKeywords)5190 NS_IMETHODIMP nsMsgDBFolder::AddKeywordsToMessages(
5191     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
5192     const nsACString& aKeywords) {
5193   nsresult rv = NS_OK;
5194   GetDatabase();
5195   if (mDatabase) {
5196     nsCString keywords;
5197 
5198     for (auto message : aMessages) {
5199       message->GetStringProperty("keywords", getter_Copies(keywords));
5200       nsTArray<nsCString> keywordArray;
5201       ParseString(aKeywords, ' ', keywordArray);
5202       uint32_t addCount = 0;
5203       for (uint32_t j = 0; j < keywordArray.Length(); j++) {
5204         int32_t start, length;
5205         if (!MsgFindKeyword(keywordArray[j], keywords, &start, &length)) {
5206           if (!keywords.IsEmpty()) keywords.Append(' ');
5207           keywords.Append(keywordArray[j]);
5208           addCount++;
5209         }
5210       }
5211       // avoid using the message key to set the string property, because
5212       // in the case of filters running on incoming pop3 mail with quarantining
5213       // turned on, the message key is wrong.
5214       mDatabase->SetStringPropertyByHdr(message, "keywords", keywords.get());
5215 
5216       if (addCount) NotifyPropertyFlagChanged(message, kKeywords, 0, addCount);
5217     }
5218   }
5219   return rv;
5220 }
5221 
RemoveKeywordsFromMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aMessages,const nsACString & aKeywords)5222 NS_IMETHODIMP nsMsgDBFolder::RemoveKeywordsFromMessages(
5223     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
5224     const nsACString& aKeywords) {
5225   nsresult rv = NS_OK;
5226   GetDatabase();
5227   if (mDatabase) {
5228     NS_ENSURE_SUCCESS(rv, rv);
5229     nsTArray<nsCString> keywordArray;
5230     ParseString(aKeywords, ' ', keywordArray);
5231     nsCString keywords;
5232     // If the tag is also a label, we should remove the label too...
5233 
5234     for (auto message : aMessages) {
5235       rv = message->GetStringProperty("keywords", getter_Copies(keywords));
5236       uint32_t removeCount = 0;
5237       for (uint32_t j = 0; j < keywordArray.Length(); j++) {
5238         bool keywordIsLabel = (StringBeginsWith(keywordArray[j], "$label"_ns) &&
5239                                keywordArray[j].CharAt(6) >= '1' &&
5240                                keywordArray[j].CharAt(6) <= '5');
5241         if (keywordIsLabel) {
5242           nsMsgLabelValue labelValue;
5243           message->GetLabel(&labelValue);
5244           // if we're removing the keyword that corresponds to a pre 2.0 label,
5245           // we need to clear the old label attribute on the message.
5246           if (labelValue == (nsMsgLabelValue)(keywordArray[j].CharAt(6) - '0'))
5247             message->SetLabel((nsMsgLabelValue)0);
5248         }
5249         int32_t startOffset, length;
5250         if (MsgFindKeyword(keywordArray[j], keywords, &startOffset, &length)) {
5251           // delete any leading space delimiters
5252           while (startOffset && keywords.CharAt(startOffset - 1) == ' ') {
5253             startOffset--;
5254             length++;
5255           }
5256           // but if the keyword is at the start then delete the following space
5257           if (!startOffset &&
5258               length < static_cast<int32_t>(keywords.Length()) &&
5259               keywords.CharAt(length) == ' ')
5260             length++;
5261           keywords.Cut(startOffset, length);
5262           removeCount++;
5263         }
5264       }
5265 
5266       if (removeCount) {
5267         mDatabase->SetStringPropertyByHdr(message, "keywords", keywords.get());
5268         NotifyPropertyFlagChanged(message, kKeywords, removeCount, 0);
5269       }
5270     }
5271   }
5272   return rv;
5273 }
5274 
GetCustomIdentity(nsIMsgIdentity ** aIdentity)5275 NS_IMETHODIMP nsMsgDBFolder::GetCustomIdentity(nsIMsgIdentity** aIdentity) {
5276   NS_ENSURE_ARG_POINTER(aIdentity);
5277   *aIdentity = nullptr;
5278   return NS_OK;
5279 }
5280 
GetProcessingFlags(nsMsgKey aKey,uint32_t * aFlags)5281 NS_IMETHODIMP nsMsgDBFolder::GetProcessingFlags(nsMsgKey aKey,
5282                                                 uint32_t* aFlags) {
5283   NS_ENSURE_ARG_POINTER(aFlags);
5284   *aFlags = 0;
5285   for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
5286     if (mProcessingFlag[i].keys && mProcessingFlag[i].keys->IsMember(aKey))
5287       *aFlags |= mProcessingFlag[i].bit;
5288   return NS_OK;
5289 }
5290 
OrProcessingFlags(nsMsgKey aKey,uint32_t mask)5291 NS_IMETHODIMP nsMsgDBFolder::OrProcessingFlags(nsMsgKey aKey, uint32_t mask) {
5292   for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
5293     if (mProcessingFlag[i].bit & mask && mProcessingFlag[i].keys)
5294       mProcessingFlag[i].keys->Add(aKey);
5295   return NS_OK;
5296 }
5297 
AndProcessingFlags(nsMsgKey aKey,uint32_t mask)5298 NS_IMETHODIMP nsMsgDBFolder::AndProcessingFlags(nsMsgKey aKey, uint32_t mask) {
5299   for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
5300     if (!(mProcessingFlag[i].bit & mask) && mProcessingFlag[i].keys)
5301       mProcessingFlag[i].keys->Remove(aKey);
5302   return NS_OK;
5303 }
5304 
5305 // Each implementation must provide an override of this, connecting the folder
5306 // type to the corresponding incoming server type.
GetIncomingServerType(nsACString & aIncomingServerType)5307 NS_IMETHODIMP nsMsgDBFolder::GetIncomingServerType(
5308     nsACString& aIncomingServerType) {
5309   NS_ASSERTION(false, "subclasses need to override this");
5310   return NS_ERROR_NOT_IMPLEMENTED;
5311 }
5312 
ClearProcessingFlags()5313 void nsMsgDBFolder::ClearProcessingFlags() {
5314   for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++) {
5315     // There is no clear method so we need to delete and re-create.
5316     delete mProcessingFlag[i].keys;
5317     mProcessingFlag[i].keys = nsMsgKeySetU::Create();
5318   }
5319 }
5320 
MessagesInKeyOrder(nsTArray<nsMsgKey> const & aKeyArray,nsIMsgFolder * srcFolder,nsTArray<RefPtr<nsIMsgDBHdr>> & messages)5321 nsresult nsMsgDBFolder::MessagesInKeyOrder(
5322     nsTArray<nsMsgKey> const& aKeyArray, nsIMsgFolder* srcFolder,
5323     nsTArray<RefPtr<nsIMsgDBHdr>>& messages) {
5324   messages.Clear();
5325   messages.SetCapacity(aKeyArray.Length());
5326 
5327   nsCOMPtr<nsIDBFolderInfo> folderInfo;
5328   nsCOMPtr<nsIMsgDatabase> db;
5329   nsresult rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
5330                                                 getter_AddRefs(db));
5331   if (NS_SUCCEEDED(rv) && db) {
5332     for (auto key : aKeyArray) {
5333       nsCOMPtr<nsIMsgDBHdr> msgHdr;
5334       rv = db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
5335       NS_ENSURE_SUCCESS(rv, rv);
5336       if (msgHdr) messages.AppendElement(msgHdr);
5337     }
5338   }
5339   return rv;
5340 }
5341 
Create()5342 /* static */ nsMsgKeySetU* nsMsgKeySetU::Create() {
5343   nsMsgKeySetU* set = new nsMsgKeySetU;
5344   if (set) {
5345     set->loKeySet = nsMsgKeySet::Create();
5346     set->hiKeySet = nsMsgKeySet::Create();
5347     if (!(set->loKeySet && set->hiKeySet)) {
5348       delete set;
5349       set = nullptr;
5350     }
5351   }
5352   return set;
5353 }
5354 
nsMsgKeySetU()5355 nsMsgKeySetU::nsMsgKeySetU() {}
5356 
~nsMsgKeySetU()5357 nsMsgKeySetU::~nsMsgKeySetU() {
5358   delete loKeySet;
5359   delete hiKeySet;
5360 }
5361 
5362 const uint32_t kLowerBits = 0x7fffffff;
5363 
Add(nsMsgKey aKey)5364 int nsMsgKeySetU::Add(nsMsgKey aKey) {
5365   int32_t intKey = static_cast<int32_t>(aKey);
5366   if (intKey >= 0) return loKeySet->Add(intKey);
5367   return hiKeySet->Add(intKey & kLowerBits);
5368 }
5369 
Remove(nsMsgKey aKey)5370 int nsMsgKeySetU::Remove(nsMsgKey aKey) {
5371   int32_t intKey = static_cast<int32_t>(aKey);
5372   if (intKey >= 0) return loKeySet->Remove(intKey);
5373   return hiKeySet->Remove(intKey & kLowerBits);
5374 }
5375 
IsMember(nsMsgKey aKey)5376 bool nsMsgKeySetU::IsMember(nsMsgKey aKey) {
5377   int32_t intKey = static_cast<int32_t>(aKey);
5378   if (intKey >= 0) return loKeySet->IsMember(intKey);
5379   return hiKeySet->IsMember(intKey & kLowerBits);
5380 }
5381 
ToMsgKeyArray(nsTArray<nsMsgKey> & aArray)5382 nsresult nsMsgKeySetU::ToMsgKeyArray(nsTArray<nsMsgKey>& aArray) {
5383   nsresult rv = loKeySet->ToMsgKeyArray(aArray);
5384   NS_ENSURE_SUCCESS(rv, rv);
5385   return hiKeySet->ToMsgKeyArray(aArray);
5386 }
5387