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