1 /* -*- Mode:
2  C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "msgCore.h"
8 #include "prmem.h"
9 #include "nsMsgImapCID.h"
10 #include "nsImapMailFolder.h"
11 #include "nsIImapService.h"
12 #include "nsIFile.h"
13 #include "nsIUrlListener.h"
14 #include "nsCOMPtr.h"
15 #include "nsMsgDBCID.h"
16 #include "nsMsgFolderFlags.h"
17 #include "nsISeekableStream.h"
18 #include "nsThreadUtils.h"
19 #include "nsIImapUrl.h"
20 #include "nsImapUtils.h"
21 #include "nsMsgUtils.h"
22 #include "nsIMsgMailSession.h"
23 #include "nsMsgBaseCID.h"
24 #include "nsMsgLocalCID.h"
25 #include "nsITransactionManager.h"
26 #include "nsImapUndoTxn.h"
27 #include "nsIImapHostSessionList.h"
28 #include "nsIMsgCopyService.h"
29 #include "nsICopyMessageStreamListener.h"
30 #include "nsImapStringBundle.h"
31 #include "nsIMsgFolderCacheElement.h"
32 #include "nsTextFormatter.h"
33 #include "nsIPrefBranch.h"
34 #include "nsIPrefService.h"
35 #include "nsMsgI18N.h"
36 #include "nsIMsgFilter.h"
37 #include "nsIMsgFilterService.h"
38 #include "nsIMsgSearchCustomTerm.h"
39 #include "nsIMsgSearchTerm.h"
40 #include "nsImapMoveCoalescer.h"
41 #include "nsIPrompt.h"
42 #include "nsIDocShell.h"
43 #include "nsUnicharUtils.h"
44 #include "nsIImapFlagAndUidState.h"
45 #include "nsIImapHeaderXferInfo.h"
46 #include "nsIMessenger.h"
47 #include "nsIMsgSearchAdapter.h"
48 #include "nsIImapMockChannel.h"
49 #include "nsIProgressEventSink.h"
50 #include "nsIMsgWindow.h"
51 #include "nsIMsgFolder.h"  // TO include biffState enum. Change to bool later...
52 #include "nsIMsgLocalMailFolder.h"
53 #include "nsIMsgOfflineImapOperation.h"
54 #include "nsImapOfflineSync.h"
55 #include "nsIImapMailFolderSink.h"
56 #include "nsIImapServerSink.h"
57 #include "nsIMsgAccountManager.h"
58 #include "nsQuickSort.h"
59 #include "nsIImapMockChannel.h"
60 #include "nsNetUtil.h"
61 #include "nsImapNamespace.h"
62 #include "nsIMsgFolderCompactor.h"
63 #include "nsMsgMessageFlags.h"
64 #include "nsISpamSettings.h"
65 #include <time.h>
66 #include "nsIMsgMailNewsUrl.h"
67 #include "nsEmbedCID.h"
68 #include "nsIMsgComposeService.h"
69 #include "nsMsgCompCID.h"
70 #include "nsDirectoryServiceDefs.h"
71 #include "nsIDirectoryEnumerator.h"
72 #include "nsIMsgIdentity.h"
73 #include "nsIMsgFolderNotificationService.h"
74 #include "nsNativeCharsetUtils.h"
75 #include "nsIExternalProtocolService.h"
76 #include "nsCExternalHandlerService.h"
77 #include "prprf.h"
78 #include "nsAutoSyncManager.h"
79 #include "nsIMsgFilterCustomAction.h"
80 #include "nsMsgReadStateTxn.h"
81 #include "nsStringEnumerator.h"
82 #include "nsIMsgStatusFeedback.h"
83 #include "nsMsgLineBuffer.h"
84 #include "mozilla/Logging.h"
85 #include "mozilla/Attributes.h"
86 #include "nsStringStream.h"
87 #include "nsIStreamListener.h"
88 
89 static NS_DEFINE_CID(kParseMailMsgStateCID, NS_PARSEMAILMSGSTATE_CID);
90 static NS_DEFINE_CID(kCImapHostSessionList, NS_IIMAPHOSTSESSIONLIST_CID);
91 
92 #define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders"
93 
94 using namespace mozilla;
95 
96 extern LazyLogModule gAutoSyncLog;  // defined in nsAutoSyncManager.cpp
97 extern LazyLogModule IMAP;          // defined in nsImapProtocol.cpp
98 extern LazyLogModule IMAP_CS;  // For CONDSTORE, defined in nsImapProtocol.cpp
99 extern LazyLogModule FILTERLOGMODULE;  // defined in nsMsgFilterService.cpp
100 LazyLogModule IMAP_KW("IMAP_KW");      // for logging keyword (tag) processing
101 
102 /*
103     Copies the contents of srcDir into destDir.
104     destDir will be created if it doesn't exist.
105 */
106 
RecursiveCopy(nsIFile * srcDir,nsIFile * destDir)107 static nsresult RecursiveCopy(nsIFile* srcDir, nsIFile* destDir) {
108   nsresult rv;
109   bool isDir;
110 
111   rv = srcDir->IsDirectory(&isDir);
112   if (NS_FAILED(rv)) return rv;
113   if (!isDir) return NS_ERROR_INVALID_ARG;
114 
115   bool exists;
116   rv = destDir->Exists(&exists);
117   if (NS_SUCCEEDED(rv) && !exists)
118     rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
119   if (NS_FAILED(rv)) return rv;
120 
121   nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
122   rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
123   if (NS_FAILED(rv)) return rv;
124 
125   bool hasMore = false;
126   while (NS_SUCCEEDED(dirIterator->HasMoreElements(&hasMore)) && hasMore) {
127     nsCOMPtr<nsIFile> dirEntry;
128     rv = dirIterator->GetNextFile(getter_AddRefs(dirEntry));
129     if (NS_SUCCEEDED(rv) && dirEntry) {
130       rv = dirEntry->IsDirectory(&isDir);
131       if (NS_SUCCEEDED(rv)) {
132         if (isDir) {
133           nsCOMPtr<nsIFile> newChild;
134           rv = destDir->Clone(getter_AddRefs(newChild));
135           if (NS_SUCCEEDED(rv)) {
136             nsAutoString leafName;
137             dirEntry->GetLeafName(leafName);
138             newChild->AppendRelativePath(leafName);
139             rv = newChild->Exists(&exists);
140             if (NS_SUCCEEDED(rv) && !exists)
141               rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775);
142             rv = RecursiveCopy(dirEntry, newChild);
143           }
144         } else
145           rv = dirEntry->CopyTo(destDir, EmptyString());
146       }
147     }
148   }
149 
150   return rv;
151 }
152 
153 //
154 //  nsMsgQuota
155 //
NS_IMPL_ISUPPORTS(nsMsgQuota,nsIMsgQuota)156 NS_IMPL_ISUPPORTS(nsMsgQuota, nsIMsgQuota)
157 
158 nsMsgQuota::nsMsgQuota(const nsACString& aName, const uint64_t& aUsage,
159                        const uint64_t& aLimit)
160     : mName(aName), mUsage(aUsage), mLimit(aLimit) {}
161 
~nsMsgQuota()162 nsMsgQuota::~nsMsgQuota() {}
163 
164 /**
165  * Note: These quota access function are not called but still must be defined
166  * for the linker.
167  */
GetName(nsACString & aName)168 NS_IMETHODIMP nsMsgQuota::GetName(nsACString& aName) {
169   aName = mName;
170   return NS_OK;
171 }
172 
SetName(const nsACString & aName)173 NS_IMETHODIMP nsMsgQuota::SetName(const nsACString& aName) {
174   mName = aName;
175   return NS_OK;
176 }
177 
GetUsage(uint64_t * aUsage)178 NS_IMETHODIMP nsMsgQuota::GetUsage(uint64_t* aUsage) {
179   *aUsage = mUsage;
180   return NS_OK;
181 }
182 
SetUsage(uint64_t aUsage)183 NS_IMETHODIMP nsMsgQuota::SetUsage(uint64_t aUsage) {
184   mUsage = aUsage;
185   return NS_OK;
186 }
187 
GetLimit(uint64_t * aLimit)188 NS_IMETHODIMP nsMsgQuota::GetLimit(uint64_t* aLimit) {
189   *aLimit = mLimit;
190   return NS_OK;
191 }
192 
SetLimit(uint64_t aLimit)193 NS_IMETHODIMP nsMsgQuota::SetLimit(uint64_t aLimit) {
194   mLimit = aLimit;
195   return NS_OK;
196 }
197 
198 //
199 //  nsImapMailFolder
200 //
nsImapMailFolder()201 nsImapMailFolder::nsImapMailFolder()
202     : m_initialized(false),
203       m_haveDiscoveredAllFolders(false),
204       m_curMsgUid(0),
205       m_nextMessageByteLength(0),
206       m_urlRunning(false),
207       m_verifiedAsOnlineFolder(false),
208       m_explicitlyVerify(false),
209       m_folderIsNamespace(false),
210       m_folderNeedsSubscribing(false),
211       m_folderNeedsAdded(false),
212       m_folderNeedsACLListed(true),
213       m_performingBiff(false),
214       m_updatingFolder(false),
215       m_compactingOfflineStore(false),
216       m_expunging(false),
217       m_applyIncomingFilters(false),
218       m_downloadingFolderForOfflineUse(false),
219       m_filterListRequiresBody(false),
220       m_folderQuotaCommandIssued(false),
221       m_folderQuotaDataIsValid(false) {
222   m_boxFlags = 0;
223   m_uidValidity = kUidUnknown;
224   m_numServerRecentMessages = 0;
225   m_numServerUnseenMessages = 0;
226   m_numServerTotalMessages = 0;
227   m_nextUID = nsMsgKey_None;
228   m_hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
229   m_folderACL = nullptr;
230   m_aclFlags = 0;
231   m_supportedUserFlags = 0;
232   m_namespace = nullptr;
233   m_pendingPlaybackReq = nullptr;
234 }
235 
~nsImapMailFolder()236 nsImapMailFolder::~nsImapMailFolder() {
237   delete m_folderACL;
238 
239   // cleanup any pending request
240   delete m_pendingPlaybackReq;
241 }
242 
NS_IMPL_ADDREF_INHERITED(nsImapMailFolder,nsMsgDBFolder)243 NS_IMPL_ADDREF_INHERITED(nsImapMailFolder, nsMsgDBFolder)
244 NS_IMPL_RELEASE_INHERITED(nsImapMailFolder, nsMsgDBFolder)
245 NS_IMPL_QUERY_HEAD(nsImapMailFolder)
246 NS_IMPL_QUERY_BODY(nsIMsgImapMailFolder)
247 NS_IMPL_QUERY_BODY(nsICopyMessageListener)
248 NS_IMPL_QUERY_BODY(nsIImapMailFolderSink)
249 NS_IMPL_QUERY_BODY(nsIImapMessageSink)
250 NS_IMPL_QUERY_BODY(nsIUrlListener)
251 NS_IMPL_QUERY_BODY(nsIMsgFilterHitNotify)
252 NS_IMPL_QUERY_TAIL_INHERITING(nsMsgDBFolder)
253 
254 nsresult nsImapMailFolder::AddDirectorySeparator(nsIFile* path) {
255   if (mURI.Equals(kImapRootURI)) {
256     // don't concat the full separator with .sbd
257   } else {
258     // see if there's a dir with the same name ending with .sbd
259     nsAutoString leafName;
260     path->GetLeafName(leafName);
261     leafName.AppendLiteral(FOLDER_SUFFIX);
262     path->SetLeafName(leafName);
263   }
264 
265   return NS_OK;
266 }
267 
nsShouldIgnoreFile(nsString & name)268 static bool nsShouldIgnoreFile(nsString& name) {
269   if (StringEndsWith(name, NS_LITERAL_STRING_FROM_CSTRING(SUMMARY_SUFFIX),
270                      nsCaseInsensitiveStringComparator)) {
271     name.SetLength(name.Length() -
272                    SUMMARY_SUFFIX_LENGTH);  // truncate the string
273     return false;
274   }
275   return true;
276 }
277 
CreateChildFromURI(const nsACString & uri,nsIMsgFolder ** folder)278 nsresult nsImapMailFolder::CreateChildFromURI(const nsACString& uri,
279                                               nsIMsgFolder** folder) {
280   nsImapMailFolder* newFolder = new nsImapMailFolder;
281   newFolder->Init(uri);
282   NS_ADDREF(*folder = newFolder);
283   return NS_OK;
284 }
285 
AddSubfolder(const nsAString & aName,nsIMsgFolder ** aChild)286 NS_IMETHODIMP nsImapMailFolder::AddSubfolder(const nsAString& aName,
287                                              nsIMsgFolder** aChild) {
288   NS_ENSURE_ARG_POINTER(aChild);
289 
290   int32_t flags = 0;
291   nsresult rv;
292 
293   nsAutoCString uri(mURI);
294   uri.Append('/');
295 
296   nsAutoCString escapedName;
297   rv = NS_MsgEscapeEncodeURLPath(aName, escapedName);
298   NS_ENSURE_SUCCESS(rv, rv);
299 
300   uri += escapedName.get();
301 
302   nsCOMPtr<nsIMsgFolder> msgFolder;
303   rv = GetChildWithURI(uri, false /*deep*/, true /*case Insensitive*/,
304                        getter_AddRefs(msgFolder));
305   if (NS_SUCCEEDED(rv) && msgFolder) return NS_MSG_FOLDER_EXISTS;
306 
307   nsCOMPtr<nsIMsgFolder> folder;
308   rv = GetOrCreateFolder(uri, getter_AddRefs(folder));
309   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
310 
311   nsCOMPtr<nsIFile> path;
312   rv = CreateDirectoryForFolder(getter_AddRefs(path));
313   NS_ENSURE_SUCCESS(rv, rv);
314 
315   folder->GetFlags((uint32_t*)&flags);
316 
317   flags |= nsMsgFolderFlags::Mail;
318 
319   nsCOMPtr<nsIImapIncomingServer> imapServer;
320   GetImapIncomingServer(getter_AddRefs(imapServer));
321   if (imapServer) {
322     bool setNewFoldersForOffline = false;
323     rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
324     if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
325       flags |= nsMsgFolderFlags::Offline;
326   }
327 
328   folder->SetParent(this);
329 
330   folder->SetFlags(flags);
331 
332   mSubFolders.AppendObject(folder);
333   folder.forget(aChild);
334 
335   nsCOMPtr<nsIMsgImapMailFolder> imapChild = do_QueryInterface(*aChild);
336   if (imapChild) {
337     imapChild->SetOnlineName(NS_ConvertUTF16toUTF8(aName));
338     imapChild->SetHierarchyDelimiter(m_hierarchyDelimiter);
339   }
340   NotifyItemAdded(*aChild);
341   return rv;
342 }
343 
344 // Creates a new child nsIMsgFolder locally, with no IMAP traffic.
AddSubfolderWithPath(nsAString & name,nsIFile * dbPath,nsIMsgFolder ** child,bool brandNew)345 nsresult nsImapMailFolder::AddSubfolderWithPath(nsAString& name,
346                                                 nsIFile* dbPath,
347                                                 nsIMsgFolder** child,
348                                                 bool brandNew) {
349   NS_ENSURE_ARG_POINTER(child);
350   nsresult rv;
351 
352   nsAutoCString uri(mURI);
353   uri.Append('/');
354   AppendUTF16toUTF8(name, uri);
355 
356   bool isServer;
357   rv = GetIsServer(&isServer);
358   NS_ENSURE_SUCCESS(rv, rv);
359 
360   bool isInbox = isServer && name.LowerCaseEqualsLiteral("inbox");
361 
362   // will make sure mSubFolders does not have duplicates because of bogus msf
363   // files.
364   nsCOMPtr<nsIMsgFolder> msgFolder;
365   rv = GetChildWithURI(uri, false /*deep*/, isInbox /*case Insensitive*/,
366                        getter_AddRefs(msgFolder));
367   if (NS_SUCCEEDED(rv) && msgFolder) return NS_MSG_FOLDER_EXISTS;
368 
369   nsCOMPtr<nsIMsgFolder> folder;
370   rv = GetOrCreateFolder(uri, getter_AddRefs(folder));
371   NS_ENSURE_SUCCESS(rv, rv);
372 
373   folder->SetFilePath(dbPath);
374   nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
375   mozilla::Unused << imapFolder;
376   NS_ENSURE_SUCCESS(rv, rv);
377 
378   uint32_t flags = 0;
379   folder->GetFlags(&flags);
380 
381   folder->SetParent(this);
382   flags |= nsMsgFolderFlags::Mail;
383 
384   uint32_t pFlags;
385   GetFlags(&pFlags);
386   bool isParentInbox = pFlags & nsMsgFolderFlags::Inbox;
387 
388   nsCOMPtr<nsIImapIncomingServer> imapServer;
389   rv = GetImapIncomingServer(getter_AddRefs(imapServer));
390   NS_ENSURE_SUCCESS(rv, rv);
391 
392   // Only set these if these are top level children or parent is inbox
393   if (isInbox)
394     flags |= nsMsgFolderFlags::Inbox;
395   else if (isServer || isParentInbox) {
396     nsMsgImapDeleteModel deleteModel;
397     imapServer->GetDeleteModel(&deleteModel);
398     if (deleteModel == nsMsgImapDeleteModels::MoveToTrash) {
399       nsAutoString trashName;
400       GetTrashFolderName(trashName);
401       if (name.Equals(trashName)) flags |= nsMsgFolderFlags::Trash;
402     }
403   }
404 
405   // Make the folder offline if it is newly created and the offline_download
406   // pref is true, unless it's the Trash or Junk folder.
407   if (brandNew &&
408       !(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
409     bool setNewFoldersForOffline = false;
410     rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
411     if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
412       flags |= nsMsgFolderFlags::Offline;
413   }
414 
415   folder->SetFlags(flags);
416 
417   if (folder) mSubFolders.AppendObject(folder);
418   folder.forget(child);
419   return NS_OK;
420 }
421 
422 // Create child nsIMsgFolders by scanning the filesystem to find .msf files.
423 // No IMAP traffic.
CreateSubFolders(nsIFile * path)424 nsresult nsImapMailFolder::CreateSubFolders(nsIFile* path) {
425   nsresult rv = NS_OK;
426   nsCOMPtr<nsIMsgIncomingServer> server;
427   rv = GetServer(getter_AddRefs(server));
428   NS_ENSURE_SUCCESS(rv, rv);
429 
430   nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
431   rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
432   NS_ENSURE_SUCCESS(rv, rv);
433 
434   // For each .msf file in the directory...
435   bool hasMore = false;
436   while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
437          hasMore) {
438     nsCOMPtr<nsIFile> currentFolderPath;
439     rv = directoryEnumerator->GetNextFile(getter_AddRefs(currentFolderPath));
440     if (NS_FAILED(rv) || !currentFolderPath) continue;
441 
442     nsAutoString currentFolderNameStr;    // online name
443     nsAutoString currentFolderDBNameStr;  // possibly munged name
444     currentFolderPath->GetLeafName(currentFolderNameStr);
445     // Skip if not an .msf file.
446     // (NOTE: nsShouldIgnoreFile() strips the trailing ".msf" here)
447     if (nsShouldIgnoreFile(currentFolderNameStr)) continue;
448 
449     // OK, here we need to get the online name from the folder cache if we can.
450     // If we can, use that to create the sub-folder
451     nsCOMPtr<nsIFile> curFolder =
452         do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
453     NS_ENSURE_SUCCESS(rv, rv);
454     nsCOMPtr<nsIFile> dbFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
455     NS_ENSURE_SUCCESS(rv, rv);
456     dbFile->InitWithFile(currentFolderPath);
457     curFolder->InitWithFile(currentFolderPath);
458     // don't strip off the .msf in currentFolderPath.
459     currentFolderPath->SetLeafName(currentFolderNameStr);
460     currentFolderDBNameStr = currentFolderNameStr;
461     nsAutoString utfLeafName = currentFolderNameStr;
462 
463     if (curFolder) {
464       nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
465       rv = GetFolderCacheElemFromFile(dbFile, getter_AddRefs(cacheElement));
466       if (NS_SUCCEEDED(rv) && cacheElement) {
467         nsCString onlineFullUtfName;
468 
469         uint32_t folderFlags;
470         rv = cacheElement->GetInt32Property("flags", (int32_t*)&folderFlags);
471         if (NS_SUCCEEDED(rv) &&
472             folderFlags & nsMsgFolderFlags::Virtual)  // ignore virtual folders
473           continue;
474         int32_t hierarchyDelimiter;
475         rv = cacheElement->GetInt32Property("hierDelim", &hierarchyDelimiter);
476         if (NS_SUCCEEDED(rv) &&
477             hierarchyDelimiter == kOnlineHierarchySeparatorUnknown) {
478           currentFolderPath->Remove(false);
479           continue;  // blow away .msf files for folders with unknown delimiter.
480         }
481         rv = cacheElement->GetStringProperty("onlineName", onlineFullUtfName);
482         if (NS_SUCCEEDED(rv) && !onlineFullUtfName.IsEmpty()) {
483           CopyFolderNameToUTF16(onlineFullUtfName, currentFolderNameStr);
484           char delimiter = 0;
485           GetHierarchyDelimiter(&delimiter);
486           int32_t leafPos = currentFolderNameStr.RFindChar(delimiter);
487           if (leafPos > 0) currentFolderNameStr.Cut(0, leafPos + 1);
488 
489           // Take the full online name, and determine the leaf name.
490           CopyUTF8toUTF16(onlineFullUtfName, utfLeafName);
491           leafPos = utfLeafName.RFindChar(delimiter);
492           if (leafPos > 0) utfLeafName.Cut(0, leafPos + 1);
493         }
494       }
495     }
496     // make the imap folder remember the file spec it was created with.
497     nsCOMPtr<nsIFile> msfFilePath =
498         do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
499     NS_ENSURE_SUCCESS(rv, rv);
500     msfFilePath->InitWithFile(currentFolderPath);
501     if (NS_SUCCEEDED(rv) && msfFilePath) {
502       // leaf name is the db name w/o .msf (nsShouldIgnoreFile strips it off)
503       // so this trims the .msf off the file spec.
504       msfFilePath->SetLeafName(currentFolderDBNameStr);
505     }
506     // Use the name as the uri for the folder.
507     nsCOMPtr<nsIMsgFolder> child;
508     AddSubfolderWithPath(utfLeafName, msfFilePath, getter_AddRefs(child));
509     if (child) {
510       // use the unicode name as the "pretty" name. Set it so it won't be
511       // automatically computed from the URI.
512       if (!currentFolderNameStr.IsEmpty())
513         child->SetPrettyName(currentFolderNameStr);
514       child->SetMsgDatabase(nullptr);
515     }
516   }
517   return rv;
518 }
519 
GetSubFolders(nsTArray<RefPtr<nsIMsgFolder>> & folders)520 NS_IMETHODIMP nsImapMailFolder::GetSubFolders(
521     nsTArray<RefPtr<nsIMsgFolder>>& folders) {
522   bool isServer;
523   nsresult rv = GetIsServer(&isServer);
524   NS_ENSURE_SUCCESS(rv, rv);
525 
526   if (!m_initialized) {
527     nsCOMPtr<nsIFile> pathFile;
528     rv = GetFilePath(getter_AddRefs(pathFile));
529     if (NS_FAILED(rv)) return rv;
530 
531     // host directory does not need .sbd tacked on
532     if (!isServer) {
533       rv = AddDirectorySeparator(pathFile);
534       if (NS_FAILED(rv)) return rv;
535     }
536 
537     m_initialized = true;  // need to set this here to avoid infinite recursion
538                            // from CreateSubfolders.
539     // we have to treat the root folder specially, because it's name
540     // doesn't end with .sbd
541 
542     int32_t newFlags = nsMsgFolderFlags::Mail;
543     bool isDirectory = false;
544     pathFile->IsDirectory(&isDirectory);
545     if (isDirectory) {
546       newFlags |= (nsMsgFolderFlags::Directory | nsMsgFolderFlags::Elided);
547       if (!mIsServer) SetFlag(newFlags);
548       rv = CreateSubFolders(pathFile);
549     }
550     if (isServer) {
551       nsCOMPtr<nsIMsgFolder> inboxFolder;
552 
553       GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inboxFolder));
554       if (!inboxFolder) {
555         // create an inbox if we don't have one.
556         CreateClientSubfolderInfo("INBOX"_ns, kOnlineHierarchySeparatorUnknown,
557                                   0, true);
558       }
559     }
560 
561     // Force initialisation recursively.
562     for (nsIMsgFolder* f : mSubFolders) {
563       nsTArray<RefPtr<nsIMsgFolder>> dummy;
564       rv = f->GetSubFolders(dummy);
565       if (NS_FAILED(rv)) {
566         break;
567       }
568     }
569 
570     UpdateSummaryTotals(false);
571     if (NS_FAILED(rv)) return rv;
572   }
573 
574   return nsMsgDBFolder::GetSubFolders(folders);
575 }
576 
577 // Makes sure the database is open and exists.  If the database is valid then
578 // returns NS_OK.  Otherwise returns a failure error value.
GetDatabase()579 nsresult nsImapMailFolder::GetDatabase() {
580   nsresult rv = NS_OK;
581   if (!mDatabase) {
582     nsCOMPtr<nsIMsgDBService> msgDBService =
583         do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
584     NS_ENSURE_SUCCESS(rv, rv);
585 
586     // Create the database, blowing it away if it needs to be rebuilt
587     rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase));
588     if (NS_FAILED(rv))
589       rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
590 
591     NS_ENSURE_SUCCESS(rv, rv);
592 
593     // UpdateNewMessages/UpdateSummaryTotals can null mDatabase, so we save a
594     // local copy
595     nsCOMPtr<nsIMsgDatabase> database(mDatabase);
596     UpdateNewMessages();
597     if (mAddListener) database->AddListener(this);
598     UpdateSummaryTotals(true);
599     mDatabase = database;
600   }
601   return rv;
602 }
603 
UpdateFolder(nsIMsgWindow * inMsgWindow)604 NS_IMETHODIMP nsImapMailFolder::UpdateFolder(nsIMsgWindow* inMsgWindow) {
605   return UpdateFolderWithListener(inMsgWindow, nullptr);
606 }
607 
UpdateFolderWithListener(nsIMsgWindow * aMsgWindow,nsIUrlListener * aUrlListener)608 NS_IMETHODIMP nsImapMailFolder::UpdateFolderWithListener(
609     nsIMsgWindow* aMsgWindow, nsIUrlListener* aUrlListener) {
610   nsresult rv;
611   bool selectFolder = false;
612 
613   // If this is the inbox, filters will be applied. Otherwise, we test the
614   // inherited folder property "applyIncomingFilters" (which defaults to empty).
615   // If this inherited property has the string value "true", we will apply
616   // filters even if this is not the inbox folder.
617   nsCString applyIncomingFilters;
618   GetInheritedStringProperty("applyIncomingFilters", applyIncomingFilters);
619   m_applyIncomingFilters = applyIncomingFilters.EqualsLiteral("true");
620 
621   nsString folderName;
622   GetPrettyName(folderName);
623   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
624           ("(Imap) nsImapMailFolder::UpdateFolderWithListener() on folder '%s'",
625            NS_ConvertUTF16toUTF8(folderName).get()));
626   if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters) {
627     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
628             ("(Imap) Preparing filter run on folder '%s'",
629              NS_ConvertUTF16toUTF8(folderName).get()));
630 
631     if (!m_filterList) {
632       rv = GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
633       if (NS_FAILED(rv)) {
634         MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
635                 ("(Imap) Loading of filter list failed"));
636       }
637     }
638 
639     // if there's no msg window, but someone is updating the inbox, we're
640     // doing something biff-like, and may download headers, so make biff notify.
641     if (!aMsgWindow && mFlags & nsMsgFolderFlags::Inbox)
642       SetPerformingBiff(true);
643   }
644 
645   if (m_filterList) {
646     nsCString listId;
647     m_filterList->GetListId(listId);
648     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
649             ("(Imap) Preparing filter list %s", listId.get()));
650     nsCOMPtr<nsIMsgIncomingServer> server;
651     rv = GetServer(getter_AddRefs(server));
652     NS_ENSURE_SUCCESS(rv, rv);
653 
654     bool canFileMessagesOnServer = true;
655     rv = server->GetCanFileMessagesOnServer(&canFileMessagesOnServer);
656     // the mdn filter is for filing return receipts into the sent folder
657     // some servers (like AOL mail servers)
658     // can't file to the sent folder, so we don't add the filter for those
659     // servers
660     if (canFileMessagesOnServer) {
661       rv = server->ConfigureTemporaryFilters(m_filterList);
662       NS_ENSURE_SUCCESS(rv, rv);
663     }
664 
665     // If a body filter is enabled for an offline folder, delay the filter
666     // application until after message has been downloaded.
667     m_filterListRequiresBody = false;
668 
669     if (mFlags & nsMsgFolderFlags::Offline) {
670       nsCOMPtr<nsIMsgFilterService> filterService =
671           do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
672       uint32_t filterCount = 0;
673       m_filterList->GetFilterCount(&filterCount);
674       for (uint32_t index = 0; index < filterCount && !m_filterListRequiresBody;
675            ++index) {
676         nsCOMPtr<nsIMsgFilter> filter;
677         m_filterList->GetFilterAt(index, getter_AddRefs(filter));
678         if (!filter) continue;
679         nsMsgFilterTypeType filterType;
680         filter->GetFilterType(&filterType);
681         if (!(filterType & nsMsgFilterType::Incoming)) continue;
682         bool enabled = false;
683         filter->GetEnabled(&enabled);
684         if (!enabled) continue;
685         nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms;
686         filter->GetSearchTerms(searchTerms);
687         for (nsIMsgSearchTerm* term : searchTerms) {
688           nsMsgSearchAttribValue attrib;
689           rv = term->GetAttrib(&attrib);
690           NS_ENSURE_SUCCESS(rv, rv);
691           if (attrib == nsMsgSearchAttrib::Body)
692             m_filterListRequiresBody = true;
693           else if (attrib == nsMsgSearchAttrib::Custom) {
694             nsAutoCString customId;
695             rv = term->GetCustomId(customId);
696             nsCOMPtr<nsIMsgSearchCustomTerm> customTerm;
697             if (NS_SUCCEEDED(rv) && filterService)
698               rv = filterService->GetCustomTerm(customId,
699                                                 getter_AddRefs(customTerm));
700             bool needsBody = false;
701             if (NS_SUCCEEDED(rv) && customTerm)
702               rv = customTerm->GetNeedsBody(&needsBody);
703             if (NS_SUCCEEDED(rv) && needsBody) m_filterListRequiresBody = true;
704           }
705           if (m_filterListRequiresBody) {
706             break;
707           }
708         }
709 
710         // Also check if filter actions need the body, as this
711         // is supported in custom actions.
712         uint32_t numActions = 0;
713         filter->GetActionCount(&numActions);
714         for (uint32_t actionIndex = 0;
715              actionIndex < numActions && !m_filterListRequiresBody;
716              actionIndex++) {
717           nsCOMPtr<nsIMsgRuleAction> action;
718           rv = filter->GetActionAt(actionIndex, getter_AddRefs(action));
719           if (NS_FAILED(rv) || !action) continue;
720 
721           nsCOMPtr<nsIMsgFilterCustomAction> customAction;
722           rv = action->GetCustomAction(getter_AddRefs(customAction));
723           if (NS_FAILED(rv) || !customAction) continue;
724 
725           bool needsBody = false;
726           customAction->GetNeedsBody(&needsBody);
727           if (needsBody) m_filterListRequiresBody = true;
728         }
729       }
730     }
731     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
732             ("(Imap) Filters require the message body: %s",
733              (m_filterListRequiresBody ? "true" : "false")));
734   }
735 
736   selectFolder = true;
737 
738   bool isServer;
739   rv = GetIsServer(&isServer);
740   if (NS_SUCCEEDED(rv) && isServer) {
741     if (!m_haveDiscoveredAllFolders) {
742       bool hasSubFolders = false;
743       GetHasSubFolders(&hasSubFolders);
744       if (!hasSubFolders) {
745         rv = CreateClientSubfolderInfo(
746             "Inbox"_ns, kOnlineHierarchySeparatorUnknown, 0, false);
747         NS_ENSURE_SUCCESS(rv, rv);
748       }
749       m_haveDiscoveredAllFolders = true;
750     }
751     selectFolder = false;
752   }
753   rv = GetDatabase();
754   if (NS_FAILED(rv)) {
755     ThrowAlertMsg("errorGettingDB", aMsgWindow);
756     return rv;
757   }
758   bool canOpenThisFolder = true;
759   GetCanOpenFolder(&canOpenThisFolder);
760 
761   bool hasOfflineEvents = false;
762   GetFlag(nsMsgFolderFlags::OfflineEvents, &hasOfflineEvents);
763 
764   if (!WeAreOffline()) {
765     if (hasOfflineEvents) {
766       // hold a reference to the offline sync object. If ProcessNextOperation
767       // runs a url, a reference will be added to it. Otherwise, it will get
768       // destroyed when the refptr goes out of scope.
769       RefPtr<nsImapOfflineSync> goOnline =
770           new nsImapOfflineSync(aMsgWindow, this, this);
771       if (goOnline) {
772         m_urlListener = aUrlListener;
773         return goOnline->ProcessNextOperation();
774       }
775     }
776   }
777 
778   // Check it we're password protecting the local store.
779   if (!PromptForMasterPasswordIfNecessary()) return NS_ERROR_FAILURE;
780 
781   if (!canOpenThisFolder) selectFolder = false;
782   // don't run select if we can't select the folder...
783   if (NS_SUCCEEDED(rv) && !m_urlRunning && selectFolder) {
784     nsCOMPtr<nsIImapService> imapService =
785         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
786     NS_ENSURE_SUCCESS(rv, rv);
787 
788     /* Do a discovery in its own url if needed. Do before SELECT url. */
789     nsCOMPtr<nsIImapHostSessionList> hostSession =
790         do_GetService(kCImapHostSessionList, &rv);
791     if (NS_SUCCEEDED(rv) && hostSession) {
792       bool foundMailboxesAlready = false;
793       nsCString serverKey;
794       GetServerKey(serverKey);
795       hostSession->GetHaveWeEverDiscoveredFoldersForHost(serverKey.get(),
796                                                          foundMailboxesAlready);
797       if (!foundMailboxesAlready) {
798         bool discoveryInProgress = false;
799         // See if discovery in progress and not yet finished.
800         hostSession->GetDiscoveryForHostInProgress(serverKey.get(),
801                                                    discoveryInProgress);
802         if (!discoveryInProgress) {
803           nsCOMPtr<nsIMsgFolder> rootFolder;
804           rv = GetRootFolder(getter_AddRefs(rootFolder));
805           if (NS_SUCCEEDED(rv) && rootFolder) {
806             rv = imapService->DiscoverAllFolders(rootFolder, this, aMsgWindow,
807                                                  nullptr);
808             if (NS_SUCCEEDED(rv))
809               hostSession->SetDiscoveryForHostInProgress(serverKey.get(), true);
810           }
811         }
812       }
813     }
814 
815     nsCOMPtr<nsIURI> url;
816     rv = imapService->SelectFolder(this, m_urlListener, aMsgWindow,
817                                    getter_AddRefs(url));
818     if (NS_SUCCEEDED(rv)) {
819       m_urlRunning = true;
820       m_updatingFolder = true;
821     }
822     if (url) {
823       nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(url, &rv);
824       NS_ENSURE_SUCCESS(rv, rv);
825       mailnewsUrl->RegisterListener(this);
826       m_urlListener = aUrlListener;
827     }
828 
829     // Allow IMAP folder auto-compact to occur when online or offline.
830     if (aMsgWindow) AutoCompact(aMsgWindow);
831 
832     if (rv == NS_MSG_ERROR_OFFLINE || rv == NS_BINDING_ABORTED) {
833       rv = NS_OK;
834       NotifyFolderEvent(kFolderLoaded);
835     }
836   } else if (NS_SUCCEEDED(rv))  // tell the front end that the folder is loaded
837                                 // if we're not going to
838   {                             // actually run a url.
839     if (!m_updatingFolder)  // if we're already running an update url, we'll let
840                             // that one send the folder loaded
841       NotifyFolderEvent(kFolderLoaded);
842   }
843   return rv;
844 }
845 
CreateSubfolder(const nsAString & folderName,nsIMsgWindow * msgWindow)846 NS_IMETHODIMP nsImapMailFolder::CreateSubfolder(const nsAString& folderName,
847                                                 nsIMsgWindow* msgWindow) {
848   if (folderName.IsEmpty()) return NS_MSG_ERROR_INVALID_FOLDER_NAME;
849 
850   nsresult rv;
851   nsAutoString trashName;
852   GetTrashFolderName(trashName);
853   if (folderName.Equals(trashName))  // Trash , a special folder
854   {
855     ThrowAlertMsg("folderExists", msgWindow);
856     return NS_MSG_FOLDER_EXISTS;
857   }
858   if (mIsServer &&
859       folderName.LowerCaseEqualsLiteral("inbox"))  // Inbox, a special folder
860   {
861     ThrowAlertMsg("folderExists", msgWindow);
862     return NS_MSG_FOLDER_EXISTS;
863   }
864 
865   nsCOMPtr<nsIImapService> imapService =
866       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
867   NS_ENSURE_SUCCESS(rv, rv);
868 
869   return imapService->CreateFolder(this, folderName, this, nullptr);
870 }
871 
CreateClientSubfolderInfo(const nsACString & folderName,char hierarchyDelimiter,int32_t flags,bool suppressNotification)872 NS_IMETHODIMP nsImapMailFolder::CreateClientSubfolderInfo(
873     const nsACString& folderName, char hierarchyDelimiter, int32_t flags,
874     bool suppressNotification) {
875   nsresult rv = NS_OK;
876 
877   // Get a directory based on our current path.
878   nsCOMPtr<nsIFile> path;
879   rv = CreateDirectoryForFolder(getter_AddRefs(path));
880   if (NS_FAILED(rv)) return rv;
881 
882   NS_ConvertUTF8toUTF16 leafName(folderName);
883   nsAutoString folderNameStr;
884   nsAutoString parentName = leafName;
885   // use RFind, because folder can start with a delimiter and
886   // not be a leaf folder.
887   int32_t folderStart = leafName.RFindChar('/');
888   if (folderStart > 0) {
889     nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
890     nsAutoCString uri(mURI);
891     leafName.Assign(Substring(parentName, folderStart + 1));
892     parentName.SetLength(folderStart);
893 
894     rv = CreateDirectoryForFolder(getter_AddRefs(path));
895     NS_ENSURE_SUCCESS(rv, rv);
896     uri.Append('/');
897     uri.Append(NS_ConvertUTF16toUTF8(parentName));
898     nsCOMPtr<nsIMsgFolder> folder;
899     rv = GetOrCreateFolder(uri, getter_AddRefs(folder));
900     NS_ENSURE_SUCCESS(rv, rv);
901     imapFolder = do_QueryInterface(folder, &rv);
902     NS_ENSURE_SUCCESS(rv, rv);
903     nsAutoCString leafnameC;
904     CopyUTF16toUTF8(leafName, leafnameC);
905     return imapFolder->CreateClientSubfolderInfo(leafnameC, hierarchyDelimiter,
906                                                  flags, suppressNotification);
907   }
908 
909   // if we get here, it's really a leaf, and "this" is the parent.
910   folderNameStr = leafName;
911 
912   // Create an empty database for this mail folder, set its name from the user
913   nsCOMPtr<nsIMsgDatabase> mailDBFactory;
914   nsCOMPtr<nsIMsgFolder> child;
915 
916   nsCOMPtr<nsIMsgDBService> msgDBService =
917       do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
918   NS_ENSURE_SUCCESS(rv, rv);
919   nsCOMPtr<nsIMsgDatabase> unusedDB;
920   nsCOMPtr<nsIFile> dbFile;
921 
922   // warning, path will be changed
923   rv = CreateFileForDB(folderNameStr, path, getter_AddRefs(dbFile));
924   NS_ENSURE_SUCCESS(rv, rv);
925 
926   // Now let's create the actual new folder
927   rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child), true);
928   NS_ENSURE_SUCCESS(rv, rv);
929   rv = msgDBService->OpenMailDBFromFile(dbFile, child, true, true,
930                                         getter_AddRefs(unusedDB));
931   if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) rv = NS_OK;
932 
933   if (NS_SUCCEEDED(rv) && unusedDB) {
934     // need to set the folder name
935     nsCOMPtr<nsIDBFolderInfo> folderInfo;
936     rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
937     nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(child, &rv);
938     if (NS_SUCCEEDED(rv)) {
939       nsAutoCString onlineName(m_onlineFolderName);
940       if (!onlineName.IsEmpty()) onlineName.Append(hierarchyDelimiter);
941       onlineName.Append(NS_ConvertUTF16toUTF8(folderNameStr));
942       imapFolder->SetVerifiedAsOnlineFolder(true);
943       imapFolder->SetOnlineName(onlineName);
944       imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
945       imapFolder->SetBoxFlags(flags);
946 
947       // Now that the child is created and the boxflags are set we can be sure
948       // all special folder flags are known. The child may get its flags already
949       // in AddSubfolderWithPath if they were in FolderCache, but that's
950       // not always the case.
951       uint32_t flags = 0;
952       child->GetFlags(&flags);
953 
954       // Set the offline use flag for the newly created folder if the
955       // offline_download preference is true, unless it's the Trash or Junk
956       // folder.
957       if (!(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
958         nsCOMPtr<nsIImapIncomingServer> imapServer;
959         rv = GetImapIncomingServer(getter_AddRefs(imapServer));
960         NS_ENSURE_SUCCESS(rv, rv);
961         bool setNewFoldersForOffline = false;
962         rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
963         if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
964           flags |= nsMsgFolderFlags::Offline;
965       } else {
966         flags &= ~nsMsgFolderFlags::Offline;  // clear offline flag if set
967       }
968 
969       flags |= nsMsgFolderFlags::Elided;
970       child->SetFlags(flags);
971 
972       nsString unicodeName;
973       rv = CopyFolderNameToUTF16(nsCString(folderName), unicodeName);
974       if (NS_SUCCEEDED(rv)) child->SetPrettyName(unicodeName);
975 
976       // store the online name as the mailbox name in the db folder info
977       // I don't think anyone uses the mailbox name, so we'll use it
978       // to restore the online name when blowing away an imap db.
979       if (folderInfo)
980         folderInfo->SetMailboxName(NS_ConvertUTF8toUTF16(onlineName));
981     }
982 
983     unusedDB->SetSummaryValid(true);
984     unusedDB->Commit(nsMsgDBCommitType::kLargeCommit);
985     unusedDB->Close(true);
986     // don't want to hold onto this newly created db.
987     child->SetMsgDatabase(nullptr);
988   }
989 
990   if (!suppressNotification) {
991     if (NS_SUCCEEDED(rv) && child) {
992       NotifyItemAdded(child);
993       child->NotifyFolderEvent(kFolderCreateCompleted);
994       nsCOMPtr<nsIMsgFolderNotificationService> notifier(
995           do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
996       if (notifier) notifier->NotifyFolderAdded(child);
997     } else {
998       NotifyFolderEvent(kFolderCreateFailed);
999     }
1000   }
1001   return rv;
1002 }
1003 
List()1004 NS_IMETHODIMP nsImapMailFolder::List() {
1005   nsresult rv;
1006   nsCOMPtr<nsIImapService> imapService =
1007       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
1008   NS_ENSURE_SUCCESS(rv, rv);
1009   return imapService->ListFolder(this, this, nullptr);
1010 }
1011 
RemoveLocalSelf()1012 NS_IMETHODIMP nsImapMailFolder::RemoveLocalSelf() {
1013   // Kill the local folder and its storage.
1014   return nsMsgDBFolder::DeleteSelf(nullptr);
1015 }
1016 
CreateStorageIfMissing(nsIUrlListener * urlListener)1017 NS_IMETHODIMP nsImapMailFolder::CreateStorageIfMissing(
1018     nsIUrlListener* urlListener) {
1019   nsresult rv = NS_OK;
1020   nsCOMPtr<nsIMsgFolder> msgParent;
1021   GetParent(getter_AddRefs(msgParent));
1022 
1023   // parent is probably not set because *this* was probably created by rdf
1024   // and not by folder discovery. So, we have to compute the parent.
1025   if (!msgParent) {
1026     nsAutoCString folderName(mURI);
1027 
1028     int32_t leafPos = folderName.RFindChar('/');
1029     nsAutoCString parentName(folderName);
1030 
1031     if (leafPos > 0) {
1032       // If there is a hierarchy, there is a parent.
1033       // Don't strip off slash if it's the first character
1034       parentName.SetLength(leafPos);
1035       rv = GetOrCreateFolder(parentName, getter_AddRefs(msgParent));
1036       NS_ENSURE_SUCCESS(rv, rv);
1037     }
1038   }
1039   if (msgParent) {
1040     nsString folderName;
1041     GetName(folderName);
1042     nsresult rv;
1043     nsCOMPtr<nsIImapService> imapService =
1044         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
1045     NS_ENSURE_SUCCESS(rv, rv);
1046     nsCOMPtr<nsIURI> uri;
1047     imapService->EnsureFolderExists(msgParent, folderName, nullptr, urlListener,
1048                                     getter_AddRefs(uri));
1049   }
1050   return rv;
1051 }
1052 
GetVerifiedAsOnlineFolder(bool * aVerifiedAsOnlineFolder)1053 NS_IMETHODIMP nsImapMailFolder::GetVerifiedAsOnlineFolder(
1054     bool* aVerifiedAsOnlineFolder) {
1055   NS_ENSURE_ARG_POINTER(aVerifiedAsOnlineFolder);
1056   *aVerifiedAsOnlineFolder = m_verifiedAsOnlineFolder;
1057   return NS_OK;
1058 }
1059 
SetVerifiedAsOnlineFolder(bool aVerifiedAsOnlineFolder)1060 NS_IMETHODIMP nsImapMailFolder::SetVerifiedAsOnlineFolder(
1061     bool aVerifiedAsOnlineFolder) {
1062   m_verifiedAsOnlineFolder = aVerifiedAsOnlineFolder;
1063   // mark ancestors as verified as well
1064   if (aVerifiedAsOnlineFolder) {
1065     nsCOMPtr<nsIMsgFolder> parent;
1066     do {
1067       GetParent(getter_AddRefs(parent));
1068       if (parent) {
1069         nsCOMPtr<nsIMsgImapMailFolder> imapParent = do_QueryInterface(parent);
1070         if (imapParent) {
1071           bool verifiedOnline;
1072           imapParent->GetVerifiedAsOnlineFolder(&verifiedOnline);
1073           if (verifiedOnline) break;
1074           imapParent->SetVerifiedAsOnlineFolder(true);
1075         }
1076       }
1077     } while (parent);
1078   }
1079   return NS_OK;
1080 }
1081 
GetOnlineDelimiter(char * onlineDelimiter)1082 NS_IMETHODIMP nsImapMailFolder::GetOnlineDelimiter(char* onlineDelimiter) {
1083   return GetHierarchyDelimiter(onlineDelimiter);
1084 }
1085 
SetHierarchyDelimiter(char aHierarchyDelimiter)1086 NS_IMETHODIMP nsImapMailFolder::SetHierarchyDelimiter(
1087     char aHierarchyDelimiter) {
1088   m_hierarchyDelimiter = aHierarchyDelimiter;
1089   return NS_OK;
1090 }
1091 
GetHierarchyDelimiter(char * aHierarchyDelimiter)1092 NS_IMETHODIMP nsImapMailFolder::GetHierarchyDelimiter(
1093     char* aHierarchyDelimiter) {
1094   NS_ENSURE_ARG_POINTER(aHierarchyDelimiter);
1095   if (mIsServer) {
1096     // if it's the root folder, we don't know the delimiter. So look at the
1097     // first child.
1098     int32_t count = mSubFolders.Count();
1099     if (count > 0) {
1100       nsCOMPtr<nsIMsgImapMailFolder> childFolder(
1101           do_QueryInterface(mSubFolders[0]));
1102       if (childFolder) {
1103         nsresult rv = childFolder->GetHierarchyDelimiter(aHierarchyDelimiter);
1104         // some code uses m_hierarchyDelimiter directly, so we should set it.
1105         m_hierarchyDelimiter = *aHierarchyDelimiter;
1106         return rv;
1107       }
1108     }
1109   }
1110   ReadDBFolderInfo(false);  // update cache first.
1111   *aHierarchyDelimiter = m_hierarchyDelimiter;
1112   return NS_OK;
1113 }
1114 
SetBoxFlags(int32_t aBoxFlags)1115 NS_IMETHODIMP nsImapMailFolder::SetBoxFlags(int32_t aBoxFlags) {
1116   ReadDBFolderInfo(false);
1117 
1118   m_boxFlags = aBoxFlags;
1119   uint32_t newFlags = mFlags;
1120 
1121   newFlags |= nsMsgFolderFlags::ImapBox;
1122 
1123   if (m_boxFlags & kNoinferiors)
1124     newFlags |= nsMsgFolderFlags::ImapNoinferiors;
1125   else
1126     newFlags &= ~nsMsgFolderFlags::ImapNoinferiors;
1127   if (m_boxFlags & kNoselect)
1128     newFlags |= nsMsgFolderFlags::ImapNoselect;
1129   else
1130     newFlags &= ~nsMsgFolderFlags::ImapNoselect;
1131   if (m_boxFlags & kPublicMailbox)
1132     newFlags |= nsMsgFolderFlags::ImapPublic;
1133   else
1134     newFlags &= ~nsMsgFolderFlags::ImapPublic;
1135   if (m_boxFlags & kOtherUsersMailbox)
1136     newFlags |= nsMsgFolderFlags::ImapOtherUser;
1137   else
1138     newFlags &= ~nsMsgFolderFlags::ImapOtherUser;
1139   if (m_boxFlags & kPersonalMailbox)
1140     newFlags |= nsMsgFolderFlags::ImapPersonal;
1141   else
1142     newFlags &= ~nsMsgFolderFlags::ImapPersonal;
1143 
1144   // The following are all flags returned by XLIST.
1145   // nsImapIncomingServer::DiscoveryDone checks for these folders.
1146   if (m_boxFlags & kImapDrafts) newFlags |= nsMsgFolderFlags::Drafts;
1147 
1148   if (m_boxFlags & kImapSpam) newFlags |= nsMsgFolderFlags::Junk;
1149 
1150   if (m_boxFlags & kImapSent) newFlags |= nsMsgFolderFlags::SentMail;
1151 
1152   if (m_boxFlags & kImapInbox) newFlags |= nsMsgFolderFlags::Inbox;
1153 
1154   if (m_boxFlags & kImapXListTrash) {
1155     nsCOMPtr<nsIImapIncomingServer> imapServer;
1156     nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
1157     (void)GetImapIncomingServer(getter_AddRefs(imapServer));
1158     if (imapServer) imapServer->GetDeleteModel(&deleteModel);
1159     if (deleteModel == nsMsgImapDeleteModels::MoveToTrash)
1160       newFlags |= nsMsgFolderFlags::Trash;
1161   }
1162   // Treat the GMail all mail folder as the archive folder.
1163   if (m_boxFlags & (kImapAllMail | kImapArchive))
1164     newFlags |= nsMsgFolderFlags::Archive;
1165 
1166   SetFlags(newFlags);
1167   return NS_OK;
1168 }
1169 
GetBoxFlags(int32_t * aBoxFlags)1170 NS_IMETHODIMP nsImapMailFolder::GetBoxFlags(int32_t* aBoxFlags) {
1171   NS_ENSURE_ARG_POINTER(aBoxFlags);
1172   *aBoxFlags = m_boxFlags;
1173   return NS_OK;
1174 }
1175 
GetExplicitlyVerify(bool * aExplicitlyVerify)1176 NS_IMETHODIMP nsImapMailFolder::GetExplicitlyVerify(bool* aExplicitlyVerify) {
1177   NS_ENSURE_ARG_POINTER(aExplicitlyVerify);
1178   *aExplicitlyVerify = m_explicitlyVerify;
1179   return NS_OK;
1180 }
1181 
SetExplicitlyVerify(bool aExplicitlyVerify)1182 NS_IMETHODIMP nsImapMailFolder::SetExplicitlyVerify(bool aExplicitlyVerify) {
1183   m_explicitlyVerify = aExplicitlyVerify;
1184   return NS_OK;
1185 }
1186 
GetNoSelect(bool * aResult)1187 NS_IMETHODIMP nsImapMailFolder::GetNoSelect(bool* aResult) {
1188   NS_ENSURE_ARG_POINTER(aResult);
1189   return GetFlag(nsMsgFolderFlags::ImapNoselect, aResult);
1190 }
1191 
ApplyRetentionSettings()1192 NS_IMETHODIMP nsImapMailFolder::ApplyRetentionSettings() {
1193   int32_t numDaysToKeepOfflineMsgs = -1;
1194 
1195   // Check if we've limited the offline storage by age.
1196   nsCOMPtr<nsIImapIncomingServer> imapServer;
1197   nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
1198   NS_ENSURE_SUCCESS(rv, rv);
1199   imapServer->GetAutoSyncMaxAgeDays(&numDaysToKeepOfflineMsgs);
1200 
1201   nsCOMPtr<nsIMsgDatabase> holdDBOpen;
1202   if (numDaysToKeepOfflineMsgs > 0) {
1203     bool dbWasCached = mDatabase != nullptr;
1204     rv = GetDatabase();
1205     NS_ENSURE_SUCCESS(rv, rv);
1206     nsCOMPtr<nsIMsgEnumerator> hdrs;
1207     rv = mDatabase->EnumerateMessages(getter_AddRefs(hdrs));
1208     NS_ENSURE_SUCCESS(rv, rv);
1209     bool hasMore = false;
1210 
1211     PRTime cutOffDay =
1212         MsgConvertAgeInDaysToCutoffDate(numDaysToKeepOfflineMsgs);
1213 
1214     // so now cutOffDay is the PRTime cut-off point. Any offline msg with
1215     // a date less than that will get marked for pending removal.
1216     while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
1217       nsCOMPtr<nsIMsgDBHdr> header;
1218       rv = hdrs->GetNext(getter_AddRefs(header));
1219       NS_ENSURE_SUCCESS(rv, rv);
1220 
1221       uint32_t msgFlags;
1222       PRTime msgDate;
1223       header->GetFlags(&msgFlags);
1224       if (msgFlags & nsMsgMessageFlags::Offline) {
1225         header->GetDate(&msgDate);
1226         MarkPendingRemoval(header, msgDate < cutOffDay);
1227         // I'm horribly tempted to break out of the loop if we've found
1228         // a message after the cut-off date, because messages will most likely
1229         // be in date order in the db, but there are always edge cases.
1230       }
1231     }
1232     if (!dbWasCached) {
1233       holdDBOpen = mDatabase;
1234       mDatabase = nullptr;
1235     }
1236   }
1237   return nsMsgDBFolder::ApplyRetentionSettings();
1238 }
1239 
1240 /**
1241  * The listener will get called when both the online expunge and the offline
1242  * store compaction are finished (if the latter is needed).
1243  */
Compact(nsIUrlListener * aListener,nsIMsgWindow * aMsgWindow)1244 NS_IMETHODIMP nsImapMailFolder::Compact(nsIUrlListener* aListener,
1245                                         nsIMsgWindow* aMsgWindow) {
1246   GetDatabase();
1247   // now's a good time to apply the retention settings. If we do delete any
1248   // messages, the expunge is going to have to wait until the delete to
1249   // finish before it can run, but the multiple-connection protection code
1250   // should handle that.
1251   if (mDatabase) ApplyRetentionSettings();
1252 
1253   m_urlListener = aListener;
1254   // We should be able to compact the offline store now that this should
1255   // just be called by the UI.
1256   if (aMsgWindow && (mFlags & nsMsgFolderFlags::Offline)) {
1257     m_compactingOfflineStore = true;
1258     CompactOfflineStore(aMsgWindow, this);
1259   }
1260   if (WeAreOffline()) return NS_OK;
1261   m_expunging = true;
1262   return Expunge(this, aMsgWindow);
1263 }
1264 
1265 NS_IMETHODIMP
NotifyCompactCompleted()1266 nsImapMailFolder::NotifyCompactCompleted() {
1267   if (!m_expunging && m_urlListener) {
1268     m_urlListener->OnStopRunningUrl(nullptr, NS_OK);
1269     m_urlListener = nullptr;
1270   }
1271   m_compactingOfflineStore = false;
1272   return NS_OK;
1273 }
1274 
MarkPendingRemoval(nsIMsgDBHdr * aHdr,bool aMark)1275 NS_IMETHODIMP nsImapMailFolder::MarkPendingRemoval(nsIMsgDBHdr* aHdr,
1276                                                    bool aMark) {
1277   NS_ENSURE_ARG_POINTER(aHdr);
1278   uint32_t offlineMessageSize;
1279   aHdr->GetOfflineMessageSize(&offlineMessageSize);
1280   aHdr->SetStringProperty("pendingRemoval", aMark ? "1" : "");
1281   if (!aMark) return NS_OK;
1282   nsresult rv = GetDatabase();
1283   NS_ENSURE_SUCCESS(rv, rv);
1284   nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
1285   rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
1286   NS_ENSURE_SUCCESS(rv, rv);
1287   return dbFolderInfo->ChangeExpungedBytes(offlineMessageSize);
1288 }
1289 
Expunge(nsIUrlListener * aListener,nsIMsgWindow * aMsgWindow)1290 NS_IMETHODIMP nsImapMailFolder::Expunge(nsIUrlListener* aListener,
1291                                         nsIMsgWindow* aMsgWindow) {
1292   nsresult rv;
1293   nsCOMPtr<nsIImapService> imapService =
1294       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
1295   NS_ENSURE_SUCCESS(rv, rv);
1296 
1297   return imapService->Expunge(this, aListener, aMsgWindow, nullptr);
1298 }
1299 
CompactAll(nsIUrlListener * aListener,nsIMsgWindow * aMsgWindow,bool aCompactOfflineAlso)1300 NS_IMETHODIMP nsImapMailFolder::CompactAll(nsIUrlListener* aListener,
1301                                            nsIMsgWindow* aMsgWindow,
1302                                            bool aCompactOfflineAlso) {
1303   nsresult rv;
1304   nsTArray<RefPtr<nsIMsgFolder>> folderArray;
1305   nsTArray<RefPtr<nsIMsgFolder>> offlineFolderArray;
1306 
1307   nsCOMPtr<nsIMsgFolder> rootFolder;
1308   rv = GetRootFolder(getter_AddRefs(rootFolder));
1309   if (NS_SUCCEEDED(rv) && rootFolder) {
1310     nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
1311     rootFolder->GetDescendants(allDescendants);
1312     for (auto folder : allDescendants) {
1313       uint32_t folderFlags;
1314       folder->GetFlags(&folderFlags);
1315       if (!(folderFlags &
1316             (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect))) {
1317         folderArray.AppendElement(folder);
1318         if (aCompactOfflineAlso) offlineFolderArray.AppendElement(folder);
1319       }
1320     }
1321     if (folderArray.IsEmpty()) return NotifyCompactCompleted();
1322   }
1323   nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
1324       do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
1325   NS_ENSURE_SUCCESS(rv, rv);
1326   return folderCompactor->CompactFolders(folderArray, offlineFolderArray,
1327                                          aListener, aMsgWindow);
1328 }
1329 
UpdateStatus(nsIUrlListener * aListener,nsIMsgWindow * aMsgWindow)1330 NS_IMETHODIMP nsImapMailFolder::UpdateStatus(nsIUrlListener* aListener,
1331                                              nsIMsgWindow* aMsgWindow) {
1332   nsresult rv;
1333   nsCOMPtr<nsIImapService> imapService =
1334       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
1335   NS_ENSURE_SUCCESS(rv, rv);
1336 
1337   nsCOMPtr<nsIURI> uri;
1338   rv = imapService->UpdateFolderStatus(this, aListener, getter_AddRefs(uri));
1339   if (uri && !aMsgWindow) {
1340     nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(uri, &rv);
1341     NS_ENSURE_SUCCESS(rv, rv);
1342     // if no msg window, we won't put up error messages (this is almost
1343     // certainly a biff-inspired status)
1344     mailNewsUrl->SetSuppressErrorMsgs(true);
1345   }
1346   return rv;
1347 }
1348 
EmptyTrash(nsIMsgWindow * aMsgWindow,nsIUrlListener * aListener)1349 NS_IMETHODIMP nsImapMailFolder::EmptyTrash(nsIMsgWindow* aMsgWindow,
1350                                            nsIUrlListener* aListener) {
1351   nsCOMPtr<nsIMsgFolder> trashFolder;
1352   nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder));
1353   if (NS_SUCCEEDED(rv)) {
1354     nsCOMPtr<nsIMsgAccountManager> accountManager =
1355         do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
1356     NS_ENSURE_SUCCESS(rv, rv);
1357     // if we are emptying trash on exit and we are an aol server then don't
1358     // perform this operation because it's causing a hang that we haven't been
1359     // able to figure out yet this is an rtm fix and we'll look for the right
1360     // solution post rtm.
1361     bool empytingOnExit = false;
1362     accountManager->GetEmptyTrashInProgress(&empytingOnExit);
1363     if (empytingOnExit) {
1364       nsCOMPtr<nsIImapIncomingServer> imapServer;
1365       rv = GetImapIncomingServer(getter_AddRefs(imapServer));
1366       if (imapServer) {
1367         bool isAOLServer = false;
1368         imapServer->GetIsAOLServer(&isAOLServer);
1369         if (isAOLServer)
1370           return NS_ERROR_FAILURE;  // we will not be performing an empty
1371                                     // trash....
1372       }                             // if we fetched an imap server
1373     }  // if emptying trash on exit which is done through the account manager.
1374 
1375     if (WeAreOffline()) {
1376       nsCOMPtr<nsIMsgDatabase> trashDB;
1377       rv = trashFolder->GetMsgDatabase(getter_AddRefs(trashDB));
1378       if (trashDB) {
1379         nsMsgKey fakeKey;
1380         trashDB->GetNextFakeOfflineMsgKey(&fakeKey);
1381 
1382         nsCOMPtr<nsIMsgOfflineImapOperation> op;
1383         rv = trashDB->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op));
1384         trashFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
1385         op->SetOperation(nsIMsgOfflineImapOperation::kDeleteAllMsgs);
1386       }
1387       return rv;
1388     }
1389 
1390     nsCOMPtr<nsIImapService> imapService =
1391         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
1392     NS_ENSURE_SUCCESS(rv, rv);
1393 
1394     if (aListener)
1395       rv = imapService->DeleteAllMessages(trashFolder, aListener, nullptr);
1396     else {
1397       nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(trashFolder);
1398       rv = imapService->DeleteAllMessages(trashFolder, urlListener, nullptr);
1399     }
1400     // Return an error if this failed. We want the empty trash on exit code
1401     // to know if this fails so that it doesn't block waiting for empty trash to
1402     // finish.
1403     NS_ENSURE_SUCCESS(rv, rv);
1404 
1405     // Delete any subfolders under Trash.
1406     nsTArray<RefPtr<nsIMsgFolder>> subFolders;
1407     rv = trashFolder->GetSubFolders(subFolders);
1408     NS_ENSURE_SUCCESS(rv, rv);
1409     while (!subFolders.IsEmpty()) {
1410       RefPtr<nsIMsgFolder> f = subFolders.PopLastElement();
1411       rv = trashFolder->PropagateDelete(f, true, aMsgWindow);
1412       NS_ENSURE_SUCCESS(rv, rv);
1413     }
1414 
1415     nsCOMPtr<nsIDBFolderInfo> transferInfo;
1416     rv = trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo));
1417     NS_ENSURE_SUCCESS(rv, rv);
1418     // Bulk-delete all the messages by deleting the msf file and storage.
1419     // This is a little kludgy.
1420     rv = trashFolder->DeleteStorage();
1421     NS_ENSURE_SUCCESS(rv, rv);
1422     trashFolder->SetDBTransferInfo(transferInfo);
1423     trashFolder->SetSizeOnDisk(0);
1424 
1425     // The trash folder has effectively been deleted.
1426     nsCOMPtr<nsIMsgFolderNotificationService> notifier(
1427         do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
1428     if (notifier) notifier->NotifyFolderDeleted(trashFolder);
1429 
1430     return NS_OK;
1431   }
1432   return rv;
1433 }
1434 
DeleteStorage()1435 NS_IMETHODIMP nsImapMailFolder::DeleteStorage() {
1436   nsresult rv = nsMsgDBFolder::DeleteStorage();
1437 
1438   // Should notify nsIMsgFolderListeners about the folder getting deleted?
1439   return rv;
1440 }
1441 
Rename(const nsAString & newName,nsIMsgWindow * msgWindow)1442 NS_IMETHODIMP nsImapMailFolder::Rename(const nsAString& newName,
1443                                        nsIMsgWindow* msgWindow) {
1444   if (mFlags & nsMsgFolderFlags::Virtual)
1445     return nsMsgDBFolder::Rename(newName, msgWindow);
1446   nsresult rv;
1447   nsAutoString newNameStr(newName);
1448   if (newNameStr.FindChar(m_hierarchyDelimiter, 0) != kNotFound) {
1449     nsCOMPtr<nsIDocShell> docShell;
1450     if (msgWindow) msgWindow->GetRootDocShell(getter_AddRefs(docShell));
1451     if (docShell) {
1452       nsCOMPtr<nsIStringBundle> bundle;
1453       rv = IMAPGetStringBundle(getter_AddRefs(bundle));
1454       if (NS_SUCCEEDED(rv) && bundle) {
1455         AutoTArray<nsString, 1> formatStrings;
1456         formatStrings.AppendElement()->Append(m_hierarchyDelimiter);
1457         nsString alertString;
1458         rv = bundle->FormatStringFromName("imapSpecialChar2", formatStrings,
1459                                           alertString);
1460         nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
1461         // setting up the dialog title
1462         nsCOMPtr<nsIMsgIncomingServer> server;
1463         rv = GetServer(getter_AddRefs(server));
1464         NS_ENSURE_SUCCESS(rv, rv);
1465         nsString dialogTitle;
1466         nsString accountName;
1467         rv = server->GetPrettyName(accountName);
1468         NS_ENSURE_SUCCESS(rv, rv);
1469         AutoTArray<nsString, 1> titleParams = {accountName};
1470         rv = bundle->FormatStringFromName("imapAlertDialogTitle", titleParams,
1471                                           dialogTitle);
1472 
1473         if (dialog && !alertString.IsEmpty())
1474           dialog->Alert(dialogTitle.get(), alertString.get());
1475       }
1476     }
1477     return NS_ERROR_FAILURE;
1478   }
1479   nsCOMPtr<nsIImapIncomingServer> incomingImapServer;
1480   GetImapIncomingServer(getter_AddRefs(incomingImapServer));
1481   if (incomingImapServer) RecursiveCloseActiveConnections(incomingImapServer);
1482 
1483   nsCOMPtr<nsIImapService> imapService =
1484       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
1485   NS_ENSURE_SUCCESS(rv, rv);
1486   return imapService->RenameLeaf(this, newName, this, msgWindow, nullptr);
1487 }
1488 
RecursiveCloseActiveConnections(nsIImapIncomingServer * incomingImapServer)1489 NS_IMETHODIMP nsImapMailFolder::RecursiveCloseActiveConnections(
1490     nsIImapIncomingServer* incomingImapServer) {
1491   NS_ENSURE_ARG(incomingImapServer);
1492 
1493   nsCOMPtr<nsIMsgImapMailFolder> folder;
1494   int32_t count = mSubFolders.Count();
1495   for (int32_t i = 0; i < count; i++) {
1496     folder = do_QueryInterface(mSubFolders[i]);
1497     if (folder) folder->RecursiveCloseActiveConnections(incomingImapServer);
1498 
1499     incomingImapServer->CloseConnectionForFolder(mSubFolders[i]);
1500   }
1501   return NS_OK;
1502 }
1503 
1504 // this is called *after* we've done the rename on the server.
PrepareToRename()1505 NS_IMETHODIMP nsImapMailFolder::PrepareToRename() {
1506   nsCOMPtr<nsIMsgImapMailFolder> folder;
1507   int32_t count = mSubFolders.Count();
1508   for (int32_t i = 0; i < count; i++) {
1509     folder = do_QueryInterface(mSubFolders[i]);
1510     if (folder) folder->PrepareToRename();
1511   }
1512 
1513   SetOnlineName(EmptyCString());
1514   return NS_OK;
1515 }
1516 
RenameLocal(const nsACString & newName,nsIMsgFolder * parent)1517 NS_IMETHODIMP nsImapMailFolder::RenameLocal(const nsACString& newName,
1518                                             nsIMsgFolder* parent) {
1519   nsAutoCString leafname(newName);
1520   nsAutoCString parentName;
1521   // newName always in the canonical form "greatparent/parentname/leafname"
1522   int32_t leafpos = leafname.RFindChar('/');
1523   if (leafpos > 0) leafname.Cut(0, leafpos + 1);
1524   m_msgParser = nullptr;
1525   PrepareToRename();
1526   CloseAndBackupFolderDB(leafname);
1527 
1528   nsresult rv = NS_OK;
1529   nsCOMPtr<nsIFile> oldPathFile;
1530   rv = GetFilePath(getter_AddRefs(oldPathFile));
1531   if (NS_FAILED(rv)) return rv;
1532 
1533   nsCOMPtr<nsIFile> parentPathFile;
1534   rv = parent->GetFilePath(getter_AddRefs(parentPathFile));
1535   NS_ENSURE_SUCCESS(rv, rv);
1536 
1537   bool isDirectory = false;
1538   parentPathFile->IsDirectory(&isDirectory);
1539   if (!isDirectory) AddDirectorySeparator(parentPathFile);
1540 
1541   nsCOMPtr<nsIFile> dirFile;
1542 
1543   int32_t count = mSubFolders.Count();
1544   if (count > 0) rv = CreateDirectoryForFolder(getter_AddRefs(dirFile));
1545 
1546   nsCOMPtr<nsIFile> oldSummaryFile;
1547   rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile));
1548   NS_ENSURE_SUCCESS(rv, rv);
1549 
1550   nsAutoCString newNameStr;
1551   oldSummaryFile->Remove(false);
1552   if (count > 0) {
1553     newNameStr = leafname;
1554     NS_MsgHashIfNecessary(newNameStr);
1555     newNameStr.AppendLiteral(FOLDER_SUFFIX8);
1556     nsAutoCString leafName;
1557     dirFile->GetNativeLeafName(leafName);
1558     if (!leafName.Equals(newNameStr))
1559       return dirFile->MoveToNative(
1560           nullptr,
1561           newNameStr);  // in case of rename operation leaf names will differ
1562 
1563     parentPathFile->AppendNative(
1564         newNameStr);  // only for move we need to progress further in case the
1565                       // parent differs
1566     bool isDirectory = false;
1567     parentPathFile->IsDirectory(&isDirectory);
1568     if (!isDirectory) {
1569       rv = parentPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
1570       NS_ENSURE_SUCCESS(rv, rv);
1571     } else {
1572       NS_ERROR("Directory already exists.");
1573     }
1574     rv = RecursiveCopy(dirFile, parentPathFile);
1575     NS_ENSURE_SUCCESS(rv, rv);
1576     dirFile->Remove(true);  // moving folders
1577   }
1578   return rv;
1579 }
1580 
GetPrettyName(nsAString & prettyName)1581 NS_IMETHODIMP nsImapMailFolder::GetPrettyName(nsAString& prettyName) {
1582   return GetName(prettyName);
1583 }
1584 
UpdateSummaryTotals(bool force)1585 NS_IMETHODIMP nsImapMailFolder::UpdateSummaryTotals(bool force) {
1586   // bug 72871 inserted the mIsServer check for IMAP
1587   return mIsServer ? NS_OK : nsMsgDBFolder::UpdateSummaryTotals(force);
1588 }
1589 
GetDeletable(bool * deletable)1590 NS_IMETHODIMP nsImapMailFolder::GetDeletable(bool* deletable) {
1591   NS_ENSURE_ARG_POINTER(deletable);
1592 
1593   bool isServer;
1594   GetIsServer(&isServer);
1595 
1596   *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
1597   return NS_OK;
1598 }
1599 
GetSizeOnDisk(int64_t * size)1600 NS_IMETHODIMP nsImapMailFolder::GetSizeOnDisk(int64_t* size) {
1601   NS_ENSURE_ARG_POINTER(size);
1602 
1603   bool isServer = false;
1604   nsresult rv = GetIsServer(&isServer);
1605   // If this is the rootFolder, return 0 as a safe value.
1606   if (NS_FAILED(rv) || isServer) mFolderSize = 0;
1607 
1608   *size = mFolderSize;
1609   return NS_OK;
1610 }
1611 
1612 NS_IMETHODIMP
GetCanCreateSubfolders(bool * aResult)1613 nsImapMailFolder::GetCanCreateSubfolders(bool* aResult) {
1614   NS_ENSURE_ARG_POINTER(aResult);
1615   *aResult = !(mFlags &
1616                (nsMsgFolderFlags::ImapNoinferiors | nsMsgFolderFlags::Virtual));
1617 
1618   bool isServer = false;
1619   GetIsServer(&isServer);
1620   if (!isServer) {
1621     nsCOMPtr<nsIImapIncomingServer> imapServer;
1622     nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
1623     bool dualUseFolders = true;
1624     if (NS_SUCCEEDED(rv) && imapServer)
1625       imapServer->GetDualUseFolders(&dualUseFolders);
1626     if (!dualUseFolders && *aResult)
1627       *aResult = (mFlags & nsMsgFolderFlags::ImapNoselect);
1628   }
1629   return NS_OK;
1630 }
1631 
1632 NS_IMETHODIMP
GetCanSubscribe(bool * aResult)1633 nsImapMailFolder::GetCanSubscribe(bool* aResult) {
1634   NS_ENSURE_ARG_POINTER(aResult);
1635   *aResult = false;
1636 
1637   bool isImapServer = false;
1638   nsresult rv = GetIsServer(&isImapServer);
1639   if (NS_FAILED(rv)) return rv;
1640   // you can only subscribe to imap servers, not imap folders
1641   *aResult = isImapServer;
1642   return NS_OK;
1643 }
1644 
GetServerKey(nsACString & serverKey)1645 nsresult nsImapMailFolder::GetServerKey(nsACString& serverKey) {
1646   // look for matching imap folders, then pop folders
1647   nsCOMPtr<nsIMsgIncomingServer> server;
1648   nsresult rv = GetServer(getter_AddRefs(server));
1649   if (NS_SUCCEEDED(rv)) rv = server->GetKey(serverKey);
1650   return rv;
1651 }
1652 
1653 NS_IMETHODIMP
GetImapIncomingServer(nsIImapIncomingServer ** aImapIncomingServer)1654 nsImapMailFolder::GetImapIncomingServer(
1655     nsIImapIncomingServer** aImapIncomingServer) {
1656   NS_ENSURE_ARG(aImapIncomingServer);
1657   nsCOMPtr<nsIMsgIncomingServer> server;
1658   if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server) {
1659     nsCOMPtr<nsIImapIncomingServer> incomingServer = do_QueryInterface(server);
1660     NS_ENSURE_TRUE(incomingServer, NS_ERROR_NO_INTERFACE);
1661     incomingServer.forget(aImapIncomingServer);
1662     return NS_OK;
1663   }
1664   return NS_ERROR_NULL_POINTER;
1665 }
1666 
1667 NS_IMETHODIMP
AddMessageDispositionState(nsIMsgDBHdr * aMessage,nsMsgDispositionState aDispositionFlag)1668 nsImapMailFolder::AddMessageDispositionState(
1669     nsIMsgDBHdr* aMessage, nsMsgDispositionState aDispositionFlag) {
1670   nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag);
1671 
1672   // set the mark message answered flag on the server for this message...
1673   if (aMessage) {
1674     nsMsgKey msgKey;
1675     aMessage->GetMessageKey(&msgKey);
1676 
1677     if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
1678       StoreImapFlags(kImapMsgAnsweredFlag, true, {msgKey}, nullptr);
1679     else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
1680       StoreImapFlags(kImapMsgForwardedFlag, true, {msgKey}, nullptr);
1681   }
1682   return NS_OK;
1683 }
1684 
1685 NS_IMETHODIMP
MarkMessagesRead(const nsTArray<RefPtr<nsIMsgDBHdr>> & messages,bool markRead)1686 nsImapMailFolder::MarkMessagesRead(
1687     const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, bool markRead) {
1688   // tell the folder to do it, which will mark them read in the db.
1689   nsresult rv = nsMsgDBFolder::MarkMessagesRead(messages, markRead);
1690   if (NS_SUCCEEDED(rv)) {
1691     nsAutoCString messageIds;
1692     nsTArray<nsMsgKey> keysToMarkRead;
1693     rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkRead);
1694     NS_ENSURE_SUCCESS(rv, rv);
1695 
1696     StoreImapFlags(kImapMsgSeenFlag, markRead, keysToMarkRead, nullptr);
1697     rv = GetDatabase();
1698     if (NS_SUCCEEDED(rv)) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
1699   }
1700   return rv;
1701 }
1702 
1703 NS_IMETHODIMP
SetLabelForMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aMessages,nsMsgLabelValue aLabel)1704 nsImapMailFolder::SetLabelForMessages(
1705     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, nsMsgLabelValue aLabel) {
1706   nsresult rv = nsMsgDBFolder::SetLabelForMessages(aMessages, aLabel);
1707   if (NS_SUCCEEDED(rv)) {
1708     nsAutoCString messageIds;
1709     nsTArray<nsMsgKey> keysToLabel;
1710     nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keysToLabel);
1711     NS_ENSURE_SUCCESS(rv, rv);
1712     StoreImapFlags((aLabel << 9), true, keysToLabel, nullptr);
1713     rv = GetDatabase();
1714     if (NS_SUCCEEDED(rv)) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
1715   }
1716   return rv;
1717 }
1718 
1719 NS_IMETHODIMP
MarkAllMessagesRead(nsIMsgWindow * aMsgWindow)1720 nsImapMailFolder::MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) {
1721   nsresult rv = GetDatabase();
1722   if (NS_SUCCEEDED(rv)) {
1723     nsTArray<nsMsgKey> thoseMarked;
1724     EnableNotifications(allMessageCountNotifications, false);
1725     rv = mDatabase->MarkAllRead(thoseMarked);
1726     EnableNotifications(allMessageCountNotifications, true);
1727     if (NS_SUCCEEDED(rv) && thoseMarked.Length() > 0) {
1728       rv = StoreImapFlags(kImapMsgSeenFlag, true, thoseMarked, nullptr);
1729       mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
1730 
1731       // Setup a undo-state
1732       if (aMsgWindow)
1733         rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked.Elements(),
1734                                       thoseMarked.Length());
1735     }
1736   }
1737   return rv;
1738 }
1739 
MarkThreadRead(nsIMsgThread * thread)1740 NS_IMETHODIMP nsImapMailFolder::MarkThreadRead(nsIMsgThread* thread) {
1741   nsresult rv = GetDatabase();
1742   if (NS_SUCCEEDED(rv)) {
1743     nsTArray<nsMsgKey> keys;
1744     rv = mDatabase->MarkThreadRead(thread, nullptr, keys);
1745     if (NS_SUCCEEDED(rv) && keys.Length() > 0) {
1746       rv = StoreImapFlags(kImapMsgSeenFlag, true, keys, nullptr);
1747       mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
1748     }
1749   }
1750   return rv;
1751 }
1752 
ReadFromFolderCacheElem(nsIMsgFolderCacheElement * element)1753 NS_IMETHODIMP nsImapMailFolder::ReadFromFolderCacheElem(
1754     nsIMsgFolderCacheElement* element) {
1755   nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element);
1756   int32_t hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
1757   nsCString onlineName;
1758 
1759   element->GetInt32Property("boxFlags", &m_boxFlags);
1760   if (NS_SUCCEEDED(
1761           element->GetInt32Property("hierDelim", &hierarchyDelimiter)) &&
1762       hierarchyDelimiter != kOnlineHierarchySeparatorUnknown)
1763     m_hierarchyDelimiter = (char)hierarchyDelimiter;
1764   rv = element->GetStringProperty("onlineName", onlineName);
1765   if (NS_SUCCEEDED(rv) && !onlineName.IsEmpty())
1766     m_onlineFolderName.Assign(onlineName);
1767 
1768   m_aclFlags = kAclInvalid;  // init to invalid value.
1769   element->GetInt32Property("aclFlags", (int32_t*)&m_aclFlags);
1770   element->GetInt32Property("serverTotal", &m_numServerTotalMessages);
1771   element->GetInt32Property("serverUnseen", &m_numServerUnseenMessages);
1772   element->GetInt32Property("serverRecent", &m_numServerRecentMessages);
1773   element->GetInt32Property("nextUID", &m_nextUID);
1774   int32_t lastSyncTimeInSec;
1775   if (NS_FAILED(element->GetInt32Property("lastSyncTimeInSec",
1776                                           (int32_t*)&lastSyncTimeInSec)))
1777     lastSyncTimeInSec = 0U;
1778 
1779   // make sure that auto-sync state object is created
1780   InitAutoSyncState();
1781   m_autoSyncStateObj->SetLastSyncTimeInSec(lastSyncTimeInSec);
1782 
1783   return rv;
1784 }
1785 
WriteToFolderCacheElem(nsIMsgFolderCacheElement * element)1786 NS_IMETHODIMP nsImapMailFolder::WriteToFolderCacheElem(
1787     nsIMsgFolderCacheElement* element) {
1788   nsresult rv = nsMsgDBFolder::WriteToFolderCacheElem(element);
1789   element->SetInt32Property("boxFlags", m_boxFlags);
1790   element->SetInt32Property("hierDelim", (int32_t)m_hierarchyDelimiter);
1791   element->SetStringProperty("onlineName", m_onlineFolderName);
1792   element->SetInt32Property("aclFlags", (int32_t)m_aclFlags);
1793   element->SetInt32Property("serverTotal", m_numServerTotalMessages);
1794   element->SetInt32Property("serverUnseen", m_numServerUnseenMessages);
1795   element->SetInt32Property("serverRecent", m_numServerRecentMessages);
1796   if (m_nextUID != (int32_t)nsMsgKey_None)
1797     element->SetInt32Property("nextUID", m_nextUID);
1798 
1799   // store folder's last sync time
1800   if (m_autoSyncStateObj) {
1801     PRTime lastSyncTime;
1802     m_autoSyncStateObj->GetLastSyncTime(&lastSyncTime);
1803     // store in sec
1804     element->SetInt32Property("lastSyncTimeInSec",
1805                               (int32_t)(lastSyncTime / PR_USEC_PER_SEC));
1806   }
1807 
1808   return rv;
1809 }
1810 
1811 NS_IMETHODIMP
MarkMessagesFlagged(const nsTArray<RefPtr<nsIMsgDBHdr>> & messages,bool markFlagged)1812 nsImapMailFolder::MarkMessagesFlagged(
1813     const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, bool markFlagged) {
1814   nsresult rv;
1815   // tell the folder to do it, which will mark them read in the db.
1816   rv = nsMsgDBFolder::MarkMessagesFlagged(messages, markFlagged);
1817   if (NS_SUCCEEDED(rv)) {
1818     nsAutoCString messageIds;
1819     nsTArray<nsMsgKey> keysToMarkFlagged;
1820     rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkFlagged);
1821     if (NS_FAILED(rv)) return rv;
1822     rv = StoreImapFlags(kImapMsgFlaggedFlag, markFlagged, keysToMarkFlagged,
1823                         nullptr);
1824     NS_ENSURE_SUCCESS(rv, rv);
1825     rv = GetDatabase();
1826     NS_ENSURE_SUCCESS(rv, rv);
1827     mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
1828   }
1829   return rv;
1830 }
1831 
SetOnlineName(const nsACString & aOnlineFolderName)1832 NS_IMETHODIMP nsImapMailFolder::SetOnlineName(
1833     const nsACString& aOnlineFolderName) {
1834   nsresult rv;
1835   nsCOMPtr<nsIMsgDatabase> db;
1836   nsCOMPtr<nsIDBFolderInfo> folderInfo;
1837   rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
1838   // do this after GetDBFolderInfoAndDB, because it crunches m_onlineFolderName
1839   // (not sure why)
1840   m_onlineFolderName = aOnlineFolderName;
1841   if (NS_SUCCEEDED(rv) && folderInfo) {
1842     nsAutoString onlineName;
1843     CopyUTF8toUTF16(aOnlineFolderName, onlineName);
1844     rv = folderInfo->SetProperty("onlineName", onlineName);
1845     rv = folderInfo->SetMailboxName(onlineName);
1846     // so, when are we going to commit this? Definitely not every time!
1847     // We could check if the online name has changed.
1848     db->Commit(nsMsgDBCommitType::kLargeCommit);
1849   }
1850   folderInfo = nullptr;
1851   return rv;
1852 }
1853 
GetOnlineName(nsACString & aOnlineFolderName)1854 NS_IMETHODIMP nsImapMailFolder::GetOnlineName(nsACString& aOnlineFolderName) {
1855   ReadDBFolderInfo(false);  // update cache first.
1856   aOnlineFolderName = m_onlineFolderName;
1857   return NS_OK;
1858 }
1859 
1860 NS_IMETHODIMP
GetDBFolderInfoAndDB(nsIDBFolderInfo ** folderInfo,nsIMsgDatabase ** db)1861 nsImapMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo,
1862                                        nsIMsgDatabase** db) {
1863   NS_ENSURE_ARG_POINTER(folderInfo);
1864   NS_ENSURE_ARG_POINTER(db);
1865 
1866   nsresult rv = GetDatabase();
1867   if (NS_FAILED(rv)) return rv;
1868 
1869   NS_ADDREF(*db = mDatabase);
1870 
1871   rv = (*db)->GetDBFolderInfo(folderInfo);
1872   if (NS_FAILED(rv))
1873     return rv;  // GetDBFolderInfo can't return NS_OK if !folderInfo
1874 
1875   nsCString onlineName;
1876   rv = (*folderInfo)->GetCharProperty("onlineName", onlineName);
1877   if (NS_FAILED(rv)) return rv;
1878 
1879   if (!onlineName.IsEmpty())
1880     m_onlineFolderName.Assign(onlineName);
1881   else {
1882     nsAutoString autoOnlineName;
1883     (*folderInfo)->GetMailboxName(autoOnlineName);
1884     if (autoOnlineName.IsEmpty()) {
1885       nsCString uri;
1886       rv = GetURI(uri);
1887       NS_ENSURE_SUCCESS(rv, rv);
1888 
1889       nsCString hostname;
1890       rv = GetHostname(hostname);
1891       NS_ENSURE_SUCCESS(rv, rv);
1892 
1893       nsCString onlineCName;
1894       rv = nsImapURI2FullName(kImapRootURI, hostname.get(), uri.get(),
1895                               getter_Copies(onlineCName));
1896       // Note: check for unknown separator '^' only became needed
1897       // with UTF8=ACCEPT modification and haven't found why. Online name
1898       // contained the '^' delimiter and gmail said "NO" when folder under
1899       // [Gmail] is created and selected.
1900       if ((m_hierarchyDelimiter != '/') &&
1901           (m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown))
1902         onlineCName.ReplaceChar('/', m_hierarchyDelimiter);
1903       // XXX: What if online name contains slashes? Breaks?
1904       m_onlineFolderName.Assign(onlineCName);
1905       CopyUTF8toUTF16(onlineCName, autoOnlineName);
1906     }
1907     (*folderInfo)->SetProperty("onlineName", autoOnlineName);
1908   }
1909   return rv;
1910 }
1911 
1912 /* static */
BuildIdsAndKeyArray(const nsTArray<RefPtr<nsIMsgDBHdr>> & messages,nsCString & msgIds,nsTArray<nsMsgKey> & keyArray)1913 nsresult nsImapMailFolder::BuildIdsAndKeyArray(
1914     const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, nsCString& msgIds,
1915     nsTArray<nsMsgKey>& keyArray) {
1916   keyArray.Clear();
1917   keyArray.SetCapacity(messages.Length());
1918   // build up message keys.
1919   for (auto msgDBHdr : messages) {
1920     nsMsgKey key;
1921     nsresult rv = msgDBHdr->GetMessageKey(&key);
1922     if (NS_SUCCEEDED(rv)) keyArray.AppendElement(key);
1923   }
1924   return AllocateUidStringFromKeys(keyArray, msgIds);
1925 }
1926 
1927 /* static */
AllocateUidStringFromKeys(const nsTArray<nsMsgKey> & keys,nsCString & msgIds)1928 nsresult nsImapMailFolder::AllocateUidStringFromKeys(
1929     const nsTArray<nsMsgKey>& keys, nsCString& msgIds) {
1930   if (keys.IsEmpty()) return NS_ERROR_INVALID_ARG;
1931   nsresult rv = NS_OK;
1932   uint32_t startSequence;
1933   startSequence = keys[0];
1934   uint32_t curSequenceEnd = startSequence;
1935   uint32_t total = keys.Length();
1936   // sort keys and then generate ranges instead of singletons!
1937   nsTArray<nsMsgKey> sorted(keys.Clone());
1938   sorted.Sort();
1939   for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++) {
1940     uint32_t curKey = sorted[keyIndex];
1941     uint32_t nextKey =
1942         (keyIndex + 1 < total) ? sorted[keyIndex + 1] : 0xFFFFFFFF;
1943     bool lastKey = (nextKey == 0xFFFFFFFF);
1944 
1945     if (lastKey) curSequenceEnd = curKey;
1946     if (nextKey == (uint32_t)curSequenceEnd + 1 && !lastKey) {
1947       curSequenceEnd = nextKey;
1948       continue;
1949     }
1950     if (curSequenceEnd > startSequence) {
1951       AppendUid(msgIds, startSequence);
1952       msgIds += ':';
1953       AppendUid(msgIds, curSequenceEnd);
1954       if (!lastKey) msgIds += ',';
1955       startSequence = nextKey;
1956       curSequenceEnd = startSequence;
1957     } else {
1958       startSequence = nextKey;
1959       curSequenceEnd = startSequence;
1960       AppendUid(msgIds, sorted[keyIndex]);
1961       if (!lastKey) msgIds += ',';
1962     }
1963   }
1964   return rv;
1965 }
1966 
MarkMessagesImapDeleted(nsTArray<nsMsgKey> * keyArray,bool deleted,nsIMsgDatabase * db)1967 nsresult nsImapMailFolder::MarkMessagesImapDeleted(nsTArray<nsMsgKey>* keyArray,
1968                                                    bool deleted,
1969                                                    nsIMsgDatabase* db) {
1970   for (uint32_t kindex = 0; kindex < keyArray->Length(); kindex++) {
1971     nsMsgKey key = keyArray->ElementAt(kindex);
1972     db->MarkImapDeleted(key, deleted, nullptr);
1973   }
1974   return NS_OK;
1975 }
1976 
DeleteMessages(nsTArray<RefPtr<nsIMsgDBHdr>> const & msgHeaders,nsIMsgWindow * msgWindow,bool deleteStorage,bool isMove,nsIMsgCopyServiceListener * listener,bool allowUndo)1977 NS_IMETHODIMP nsImapMailFolder::DeleteMessages(
1978     nsTArray<RefPtr<nsIMsgDBHdr>> const& msgHeaders, nsIMsgWindow* msgWindow,
1979     bool deleteStorage, bool isMove, nsIMsgCopyServiceListener* listener,
1980     bool allowUndo) {
1981   // *** jt - assuming delete is move to the trash folder for now
1982   nsAutoCString uri;
1983   bool deleteImmediatelyNoTrash = false;
1984   nsAutoCString messageIds;
1985   nsTArray<nsMsgKey> srcKeyArray;
1986   bool deleteMsgs = true;  // used for toggling delete status - default is true
1987   nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
1988   imapMessageFlagsType messageFlags = kImapMsgDeletedFlag;
1989 
1990   nsCOMPtr<nsIImapIncomingServer> imapServer;
1991   nsresult rv = GetFlag(nsMsgFolderFlags::Trash, &deleteImmediatelyNoTrash);
1992   rv = GetImapIncomingServer(getter_AddRefs(imapServer));
1993 
1994   if (NS_SUCCEEDED(rv) && imapServer) {
1995     imapServer->GetDeleteModel(&deleteModel);
1996     if (deleteModel != nsMsgImapDeleteModels::MoveToTrash || deleteStorage)
1997       deleteImmediatelyNoTrash = true;
1998     // if we're deleting a message, we should pseudo-interrupt the msg
1999     // load of the current message.
2000     bool interrupted = false;
2001     imapServer->PseudoInterruptMsgLoad(this, msgWindow, &interrupted);
2002   }
2003 
2004   rv = BuildIdsAndKeyArray(msgHeaders, messageIds, srcKeyArray);
2005   if (NS_FAILED(rv)) return rv;
2006 
2007   nsCOMPtr<nsIMsgFolder> rootFolder;
2008   nsCOMPtr<nsIMsgFolder> trashFolder;
2009 
2010   if (!deleteImmediatelyNoTrash) {
2011     rv = GetRootFolder(getter_AddRefs(rootFolder));
2012     if (NS_SUCCEEDED(rv) && rootFolder) {
2013       rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
2014                                      getter_AddRefs(trashFolder));
2015       NS_ASSERTION(trashFolder, "couldn't find trash");
2016       // if we can't find the trash, we'll just have to do an imap delete and
2017       // pretend this is the trash
2018       if (!trashFolder) deleteImmediatelyNoTrash = true;
2019     }
2020   }
2021 
2022   if ((NS_SUCCEEDED(rv) && deleteImmediatelyNoTrash) ||
2023       deleteModel == nsMsgImapDeleteModels::IMAPDelete) {
2024     if (allowUndo) {
2025       // need to take care of these two delete models
2026       RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
2027       if (!undoMsgTxn ||
2028           NS_FAILED(undoMsgTxn->Init(this, &srcKeyArray, messageIds.get(),
2029                                      nullptr, true, isMove)))
2030         return NS_ERROR_OUT_OF_MEMORY;
2031 
2032       undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
2033       // we're adding this undo action before the delete is successful. This is
2034       // evil, but 4.5 did it as well.
2035       nsCOMPtr<nsITransactionManager> txnMgr;
2036       if (msgWindow) msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
2037       if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
2038     }
2039 
2040     if (deleteModel == nsMsgImapDeleteModels::IMAPDelete && !deleteStorage) {
2041       deleteMsgs = false;
2042       for (nsIMsgDBHdr* msgHdr : msgHeaders) {
2043         if (!msgHdr) {
2044           continue;
2045         }
2046         uint32_t flags;
2047         msgHdr->GetFlags(&flags);
2048         if (!(flags & nsMsgMessageFlags::IMAPDeleted)) {
2049           deleteMsgs = true;
2050           break;
2051         }
2052       }
2053     }
2054     // if copy service listener is also a url listener, pass that
2055     // url listener into StoreImapFlags.
2056     nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(listener);
2057     if (deleteMsgs) messageFlags |= kImapMsgSeenFlag;
2058     rv = StoreImapFlags(messageFlags, deleteMsgs, srcKeyArray, urlListener);
2059 
2060     if (NS_SUCCEEDED(rv)) {
2061       if (mDatabase) {
2062         nsCOMPtr<nsIMsgDatabase> database(mDatabase);
2063         if (deleteModel == nsMsgImapDeleteModels::IMAPDelete)
2064           MarkMessagesImapDeleted(&srcKeyArray, deleteMsgs, database);
2065         else {
2066           EnableNotifications(allMessageCountNotifications,
2067                               false);  //"remove it immediately" model
2068           // Notify if this is an actual delete.
2069           if (!isMove) {
2070             nsCOMPtr<nsIMsgFolderNotificationService> notifier(
2071                 do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
2072             if (notifier) notifier->NotifyMsgsDeleted(msgHeaders);
2073           }
2074           DeleteStoreMessages(msgHeaders);
2075           database->DeleteMessages(srcKeyArray, nullptr);
2076           EnableNotifications(allMessageCountNotifications, true);
2077         }
2078         if (listener) {
2079           listener->OnStartCopy();
2080           listener->OnStopCopy(NS_OK);
2081         }
2082         NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
2083       }
2084     }
2085     return rv;
2086   }
2087 
2088   // have to move the messages to the trash
2089   if (trashFolder) {
2090     nsCOMPtr<nsIMsgFolder> srcFolder;
2091     nsCOMPtr<nsISupports> srcSupport;
2092 
2093     rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(srcFolder));
2094     nsCOMPtr<nsIMsgCopyService> copyService =
2095         do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
2096     NS_ENSURE_SUCCESS(rv, rv);
2097     rv = copyService->CopyMessages(srcFolder, msgHeaders, trashFolder, true,
2098                                    listener, msgWindow, allowUndo);
2099   }
2100 
2101   return rv;
2102 }
2103 
2104 // check if folder is the trash, or a descendent of the trash
2105 // so we can tell if the folders we're deleting from it should
2106 // be *really* deleted.
TrashOrDescendentOfTrash(nsIMsgFolder * folder)2107 bool nsImapMailFolder::TrashOrDescendentOfTrash(nsIMsgFolder* folder) {
2108   NS_ENSURE_TRUE(folder, false);
2109   nsCOMPtr<nsIMsgFolder> parent;
2110   nsCOMPtr<nsIMsgFolder> curFolder = folder;
2111   nsresult rv;
2112   uint32_t flags = 0;
2113   do {
2114     rv = curFolder->GetFlags(&flags);
2115     if (NS_FAILED(rv)) return false;
2116     if (flags & nsMsgFolderFlags::Trash) return true;
2117     curFolder->GetParent(getter_AddRefs(parent));
2118     if (!parent) return false;
2119     curFolder = parent;
2120   } while (NS_SUCCEEDED(rv) && curFolder);
2121   return false;
2122 }
2123 NS_IMETHODIMP
DeleteSelf(nsIMsgWindow * msgWindow)2124 nsImapMailFolder::DeleteSelf(nsIMsgWindow* msgWindow) {
2125   nsCOMPtr<nsIMsgFolder> trashFolder;
2126   nsresult rv;
2127   uint32_t folderFlags;
2128 
2129   // No IMAP shenanigans required for virtual folders.
2130   GetFlags(&folderFlags);
2131   if (folderFlags & nsMsgFolderFlags::Virtual) {
2132     return nsMsgDBFolder::DeleteSelf(nullptr);
2133   }
2134 
2135   // "this" is the folder we're deleting from
2136   bool deleteNoTrash = TrashOrDescendentOfTrash(this) || !DeleteIsMoveToTrash();
2137   bool confirmDeletion = true;
2138 
2139   nsCOMPtr<nsIImapService> imapService =
2140       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
2141   NS_ENSURE_SUCCESS(rv, rv);
2142   if (!deleteNoTrash) {
2143     rv = GetTrashFolder(getter_AddRefs(trashFolder));
2144     // If we can't find the trash folder and we are supposed to move it to the
2145     // trash return failure.
2146     if (NS_FAILED(rv) || !trashFolder) return NS_ERROR_FAILURE;
2147     bool canHaveSubFoldersOfTrash = true;
2148     trashFolder->GetCanCreateSubfolders(&canHaveSubFoldersOfTrash);
2149     if (canHaveSubFoldersOfTrash)  // UW server doesn't set NOINFERIORS - check
2150                                    // dual use pref
2151     {
2152       nsCOMPtr<nsIImapIncomingServer> imapServer;
2153       rv = GetImapIncomingServer(getter_AddRefs(imapServer));
2154       NS_ENSURE_SUCCESS(rv, rv);
2155       bool serverSupportsDualUseFolders;
2156       imapServer->GetDualUseFolders(&serverSupportsDualUseFolders);
2157       if (!serverSupportsDualUseFolders) canHaveSubFoldersOfTrash = false;
2158     }
2159     if (!canHaveSubFoldersOfTrash) deleteNoTrash = true;
2160     nsCOMPtr<nsIPrefBranch> prefBranch(
2161         do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
2162     NS_ENSURE_SUCCESS(rv, rv);
2163     prefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash",
2164                             &confirmDeletion);
2165   }
2166 
2167   // If we are deleting folder immediately, ask user for confirmation.
2168   bool confirmed = false;
2169   if (confirmDeletion || deleteNoTrash) {
2170     nsCOMPtr<nsIStringBundle> bundle;
2171     rv = IMAPGetStringBundle(getter_AddRefs(bundle));
2172     NS_ENSURE_SUCCESS(rv, rv);
2173 
2174     nsAutoString folderName;
2175     rv = GetName(folderName);
2176     NS_ENSURE_SUCCESS(rv, rv);
2177     AutoTArray<nsString, 1> formatStrings = {folderName};
2178 
2179     nsAutoString deleteFolderDialogTitle;
2180     rv = bundle->GetStringFromName("imapDeleteFolderDialogTitle",
2181                                    deleteFolderDialogTitle);
2182     NS_ENSURE_SUCCESS(rv, rv);
2183 
2184     nsAutoString deleteFolderButtonLabel;
2185     rv = bundle->GetStringFromName("imapDeleteFolderButtonLabel",
2186                                    deleteFolderButtonLabel);
2187     NS_ENSURE_SUCCESS(rv, rv);
2188 
2189     nsAutoString confirmationStr;
2190     rv = bundle->FormatStringFromName(
2191         (deleteNoTrash) ? "imapDeleteNoTrash" : "imapMoveFolderToTrash",
2192         formatStrings, confirmationStr);
2193     NS_ENSURE_SUCCESS(rv, rv);
2194     if (!msgWindow) return NS_ERROR_NULL_POINTER;
2195     nsCOMPtr<nsIDocShell> docShell;
2196     msgWindow->GetRootDocShell(getter_AddRefs(docShell));
2197     nsCOMPtr<nsIPrompt> dialog;
2198     if (docShell) dialog = do_GetInterface(docShell);
2199     if (dialog) {
2200       int32_t buttonPressed = 0;
2201       // Default the dialog to "cancel".
2202       const uint32_t buttonFlags =
2203           (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
2204           (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);
2205 
2206       bool dummyValue = false;
2207       rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(),
2208                              confirmationStr.get(), buttonFlags,
2209                              deleteFolderButtonLabel.get(), nullptr, nullptr,
2210                              nullptr, &dummyValue, &buttonPressed);
2211       NS_ENSURE_SUCCESS(rv, rv);
2212       confirmed = !buttonPressed;  // "ok" is in position 0
2213     }
2214   } else {
2215     confirmed = true;
2216   }
2217 
2218   if (confirmed) {
2219     if (deleteNoTrash) {
2220       rv = imapService->DeleteFolder(this, this, msgWindow, nullptr);
2221       nsMsgDBFolder::DeleteSelf(msgWindow);
2222     } else {
2223       bool match = false;
2224       rv = MatchOrChangeFilterDestination(nullptr, false, &match);
2225       if (match) {
2226         bool confirm = false;
2227         ConfirmFolderDeletionForFilter(msgWindow, &confirm);
2228         if (!confirm) return NS_OK;
2229       }
2230       rv = imapService->MoveFolder(this, trashFolder, this, msgWindow, nullptr);
2231     }
2232   }
2233   return rv;
2234 }
2235 
2236 // FIXME: helper function to know whether we should check all IMAP folders
2237 // for new mail; this is necessary because of a legacy hidden preference
2238 // mail.check_all_imap_folders_for_new (now replaced by per-server preference
2239 // mail.server.%serverkey%.check_all_folders_for_new), still present in some
2240 // profiles.
2241 /*static*/
ShouldCheckAllFolders(nsIImapIncomingServer * imapServer)2242 bool nsImapMailFolder::ShouldCheckAllFolders(
2243     nsIImapIncomingServer* imapServer) {
2244   // Check legacy global preference to see if we should check all folders for
2245   // new messages, or just the inbox and marked ones.
2246   bool checkAllFolders = false;
2247   nsresult rv;
2248   nsCOMPtr<nsIPrefBranch> prefBranch =
2249       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
2250   NS_ENSURE_SUCCESS(rv, false);
2251   // This pref might not exist, which is OK.
2252   (void)prefBranch->GetBoolPref("mail.check_all_imap_folders_for_new",
2253                                 &checkAllFolders);
2254 
2255   if (checkAllFolders) return true;
2256 
2257   // If the legacy preference doesn't exist or has its default value (False),
2258   // the true preference is read.
2259   imapServer->GetCheckAllFoldersForNew(&checkAllFolders);
2260   return checkAllFolders;
2261 }
2262 
2263 // Called by Biff, or when user presses GetMsg button.
GetNewMessages(nsIMsgWindow * aWindow,nsIUrlListener * aListener)2264 NS_IMETHODIMP nsImapMailFolder::GetNewMessages(nsIMsgWindow* aWindow,
2265                                                nsIUrlListener* aListener) {
2266   nsCOMPtr<nsIMsgFolder> rootFolder;
2267   nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
2268   if (NS_SUCCEEDED(rv) && rootFolder) {
2269     nsCOMPtr<nsIImapIncomingServer> imapServer;
2270     rv = GetImapIncomingServer(getter_AddRefs(imapServer));
2271     NS_ENSURE_SUCCESS(rv, rv);
2272     bool performingBiff = false;
2273     nsCOMPtr<nsIMsgIncomingServer> incomingServer =
2274         do_QueryInterface(imapServer, &rv);
2275     NS_ENSURE_SUCCESS(rv, rv);
2276     incomingServer->GetPerformingBiff(&performingBiff);
2277     m_urlListener = aListener;
2278 
2279     // See if we should check all folders for new messages, or just the inbox
2280     // and marked ones
2281     bool checkAllFolders = ShouldCheckAllFolders(imapServer);
2282 
2283     // Get new messages for inbox
2284     nsCOMPtr<nsIMsgFolder> inbox;
2285     rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
2286                                         getter_AddRefs(inbox));
2287     if (inbox) {
2288       nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(inbox, &rv);
2289       NS_ENSURE_SUCCESS(rv, rv);
2290       imapFolder->SetPerformingBiff(performingBiff);
2291       inbox->SetGettingNewMessages(true);
2292       rv = inbox->UpdateFolder(aWindow);
2293     }
2294     // Get new messages for other folders if marked, or all of them if the pref
2295     // is set
2296     rv = imapServer->GetNewMessagesForNonInboxFolders(
2297         rootFolder, aWindow, checkAllFolders, performingBiff);
2298   }
2299   return rv;
2300 }
2301 
Shutdown(bool shutdownChildren)2302 NS_IMETHODIMP nsImapMailFolder::Shutdown(bool shutdownChildren) {
2303   m_filterList = nullptr;
2304   m_initialized = false;
2305   // mPath is used to decide if folder pathname needs to be reconstructed in
2306   // GetPath().
2307   mPath = nullptr;
2308   m_moveCoalescer = nullptr;
2309   m_msgParser = nullptr;
2310   if (m_playbackTimer) {
2311     m_playbackTimer->Cancel();
2312     m_playbackTimer = nullptr;
2313   }
2314   m_pendingOfflineMoves.Clear();
2315   return nsMsgDBFolder::Shutdown(shutdownChildren);
2316 }
2317 
GetBodysToDownload(nsTArray<nsMsgKey> * keysOfMessagesToDownload)2318 nsresult nsImapMailFolder::GetBodysToDownload(
2319     nsTArray<nsMsgKey>* keysOfMessagesToDownload) {
2320   NS_ENSURE_ARG(keysOfMessagesToDownload);
2321   NS_ENSURE_TRUE(mDatabase, NS_ERROR_FAILURE);
2322 
2323   nsCOMPtr<nsIMsgEnumerator> enumerator;
2324   nsresult rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
2325   if (NS_SUCCEEDED(rv) && enumerator) {
2326     bool hasMore;
2327     while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) &&
2328            hasMore) {
2329       nsCOMPtr<nsIMsgDBHdr> header;
2330       rv = enumerator->GetNext(getter_AddRefs(header));
2331       NS_ENSURE_SUCCESS(rv, rv);
2332       bool shouldStoreMsgOffline = false;
2333       nsMsgKey msgKey;
2334       header->GetMessageKey(&msgKey);
2335       // MsgFitsDownloadCriteria ignores nsMsgFolderFlags::Offline, which we
2336       // want
2337       if (m_downloadingFolderForOfflineUse)
2338         MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
2339       else
2340         ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
2341       if (shouldStoreMsgOffline)
2342         keysOfMessagesToDownload->AppendElement(msgKey);
2343     }
2344   }
2345   return rv;
2346 }
2347 
OnNewIdleMessages()2348 NS_IMETHODIMP nsImapMailFolder::OnNewIdleMessages() {
2349   nsresult rv;
2350   nsCOMPtr<nsIImapIncomingServer> imapServer;
2351   rv = GetImapIncomingServer(getter_AddRefs(imapServer));
2352   NS_ENSURE_SUCCESS(rv, rv);
2353 
2354   bool checkAllFolders = ShouldCheckAllFolders(imapServer);
2355 
2356   // only trigger biff if we're checking all new folders for new messages, or
2357   // this particular folder, but excluding trash,junk, sent, and no select
2358   // folders, by default.
2359   if ((checkAllFolders &&
2360        !(mFlags &
2361          (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk |
2362           nsMsgFolderFlags::SentMail | nsMsgFolderFlags::ImapNoselect))) ||
2363       (mFlags & (nsMsgFolderFlags::CheckNew | nsMsgFolderFlags::Inbox)))
2364     SetPerformingBiff(true);
2365   return UpdateFolder(nullptr);
2366 }
2367 
UpdateImapMailboxInfo(nsIImapProtocol * aProtocol,nsIMailboxSpec * aSpec)2368 NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxInfo(
2369     nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec) {
2370   nsresult rv;
2371   ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
2372   ChangeNumPendingUnread(-mNumPendingUnreadMessages);
2373   m_numServerRecentMessages = 0;  // clear this since we selected the folder.
2374   m_numServerUnseenMessages = 0;  // clear this since we selected the folder.
2375 
2376   if (!mDatabase) GetDatabase();
2377 
2378   bool folderSelected;
2379   rv = aSpec->GetFolderSelected(&folderSelected);
2380   NS_ENSURE_SUCCESS(rv, rv);
2381   nsTArray<nsMsgKey> existingKeys;
2382   nsTArray<nsMsgKey> keysToDelete;
2383   uint32_t numNewUnread;
2384   nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
2385   int32_t imapUIDValidity = 0;
2386   if (mDatabase) {
2387     rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
2388     if (NS_SUCCEEDED(rv) && dbFolderInfo) {
2389       dbFolderInfo->GetImapUidValidity(&imapUIDValidity);
2390       uint64_t mailboxHighestModSeq;
2391       aSpec->GetHighestModSeq(&mailboxHighestModSeq);
2392       MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
2393               ("UpdateImapMailboxInfo(): Store highest MODSEQ=%" PRIu64
2394                " for folder=%s",
2395                mailboxHighestModSeq, m_onlineFolderName.get()));
2396       char intStrBuf[40];
2397       PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", mailboxHighestModSeq);
2398       dbFolderInfo->SetCharProperty(kModSeqPropertyName,
2399                                     nsDependentCString(intStrBuf));
2400     }
2401     nsTArray<nsMsgKey> keys;
2402     rv = mDatabase->ListAllKeys(keys);
2403     NS_ENSURE_SUCCESS(rv, rv);
2404     existingKeys.AppendElements(keys);
2405     mDatabase->ListAllOfflineDeletes(&existingKeys);
2406   }
2407   int32_t folderValidity;
2408   aSpec->GetFolder_UIDVALIDITY(&folderValidity);
2409   nsCOMPtr<nsIImapFlagAndUidState> flagState;
2410   aSpec->GetFlagState(getter_AddRefs(flagState));
2411 
2412   // remember what the supported user flags are.
2413   uint32_t supportedUserFlags;
2414   aSpec->GetSupportedUserFlags(&supportedUserFlags);
2415   SetSupportedUserFlags(supportedUserFlags);
2416 
2417   m_uidValidity = folderValidity;
2418 
2419   if (imapUIDValidity != folderValidity) {
2420     NS_ASSERTION(imapUIDValidity == kUidUnknown,
2421                  "uid validity seems to have changed, blowing away db");
2422     nsCOMPtr<nsIFile> pathFile;
2423     rv = GetFilePath(getter_AddRefs(pathFile));
2424     if (NS_FAILED(rv)) return rv;
2425 
2426     nsCOMPtr<nsIMsgDBService> msgDBService =
2427         do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
2428     NS_ENSURE_SUCCESS(rv, rv);
2429 
2430     nsCOMPtr<nsIDBFolderInfo> transferInfo;
2431     if (dbFolderInfo)
2432       dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo));
2433 
2434     // A backup message database might have been created earlier, for example
2435     // if the user requested a reindex. We'll use the earlier one if we can,
2436     // otherwise we'll try to backup at this point.
2437     nsresult rvbackup = OpenBackupMsgDatabase();
2438     if (mDatabase) {
2439       dbFolderInfo = nullptr;
2440       if (NS_FAILED(rvbackup)) {
2441         CloseAndBackupFolderDB(EmptyCString());
2442         if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase) {
2443           mBackupDatabase->RemoveListener(this);
2444           mBackupDatabase = nullptr;
2445         }
2446       } else
2447         mDatabase->ForceClosed();
2448     }
2449     mDatabase = nullptr;
2450 
2451     nsCOMPtr<nsIFile> summaryFile;
2452     rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile));
2453     // Remove summary file.
2454     if (NS_SUCCEEDED(rv) && summaryFile) summaryFile->Remove(false);
2455 
2456     // Create a new summary file, update the folder message counts, and
2457     // Close the summary file db.
2458     rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
2459 
2460     if (NS_FAILED(rv) && mDatabase) {
2461       mDatabase->ForceClosed();
2462       mDatabase = nullptr;
2463     } else if (NS_SUCCEEDED(rv) && mDatabase) {
2464       if (transferInfo) SetDBTransferInfo(transferInfo);
2465 
2466       SummaryChanged();
2467       if (mDatabase) {
2468         if (mAddListener) mDatabase->AddListener(this);
2469         rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
2470       }
2471     }
2472     // store the new UIDVALIDITY value
2473 
2474     if (NS_SUCCEEDED(rv) && dbFolderInfo) {
2475       dbFolderInfo->SetImapUidValidity(folderValidity);
2476       // need to forget highest mod seq when uid validity rolls.
2477       MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
2478               ("UpdateImapMailboxInfo(): UIDVALIDITY changed, reset highest "
2479                "MODSEQ and UID for folder=%s",
2480                m_onlineFolderName.get()));
2481       dbFolderInfo->SetCharProperty(kModSeqPropertyName, EmptyCString());
2482       dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, 0);
2483     }
2484     // delete all my msgs, the keys are bogus now
2485     // add every message in this folder
2486     existingKeys.Clear();
2487     //      keysToDelete.CopyArray(&existingKeys);
2488 
2489     if (flagState) {
2490       nsTArray<nsMsgKey> no_existingKeys;
2491       FindKeysToAdd(no_existingKeys, m_keysToFetch, numNewUnread, flagState);
2492     }
2493     if (NS_FAILED(rv)) pathFile->Remove(false);
2494 
2495   } else if (!flagState /*&& !NET_IsOffline() */)  // if there are no messages
2496                                                    // on the server
2497     keysToDelete = existingKeys.Clone();
2498   else /* if ( !NET_IsOffline()) */
2499   {
2500     uint32_t boxFlags;
2501     aSpec->GetBox_flags(&boxFlags);
2502     // FindKeysToDelete and FindKeysToAdd require sorted lists
2503     existingKeys.Sort();
2504     FindKeysToDelete(existingKeys, keysToDelete, flagState, boxFlags);
2505     // if this is the result of an expunge then don't grab headers
2506     if (!(boxFlags & kJustExpunged))
2507       FindKeysToAdd(existingKeys, m_keysToFetch, numNewUnread, flagState);
2508   }
2509   m_totalKeysToFetch = m_keysToFetch.Length();
2510   if (!keysToDelete.IsEmpty() && mDatabase) {
2511     nsTArray<RefPtr<nsIMsgDBHdr>> hdrsToDelete;
2512     MsgGetHeadersFromKeys(mDatabase, keysToDelete, hdrsToDelete);
2513     // Notify nsIMsgFolderListeners of a mass delete, but only if we actually
2514     // have headers
2515     if (!hdrsToDelete.IsEmpty()) {
2516       nsCOMPtr<nsIMsgFolderNotificationService> notifier(
2517           do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
2518       if (notifier) notifier->NotifyMsgsDeleted(hdrsToDelete);
2519     }
2520     DeleteStoreMessages(hdrsToDelete);
2521     EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false);
2522     mDatabase->DeleteMessages(keysToDelete, nullptr);
2523     EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true);
2524   }
2525   int32_t numUnreadFromServer;
2526   aSpec->GetNumUnseenMessages(&numUnreadFromServer);
2527 
2528   bool partialUIDFetch;
2529   flagState->GetPartialUIDFetch(&partialUIDFetch);
2530 
2531   // For partial UID fetches, we can only trust the numUnread from the server.
2532   if (partialUIDFetch) numNewUnread = numUnreadFromServer;
2533 
2534   // If we are performing biff for this folder, tell the
2535   // stand-alone biff about the new high water mark
2536   if (m_performingBiff && numNewUnread) {
2537     // We must ensure that the server knows that we are performing biff.
2538     // Otherwise the stand-alone biff won't fire.
2539     nsCOMPtr<nsIMsgIncomingServer> server;
2540     if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
2541       server->SetPerformingBiff(true);
2542     SetNumNewMessages(numNewUnread);
2543   }
2544   SyncFlags(flagState);
2545   if (mDatabase && (int32_t)(mNumUnreadMessages + m_keysToFetch.Length()) >
2546                        numUnreadFromServer)
2547     mDatabase->SyncCounts();
2548 
2549   if (!m_keysToFetch.IsEmpty() && aProtocol)
2550     PrepareToAddHeadersToMailDB(aProtocol);
2551   else {
2552     bool gettingNewMessages;
2553     GetGettingNewMessages(&gettingNewMessages);
2554     if (gettingNewMessages)
2555       ProgressStatusString(aProtocol, "imapNoNewMessages", nullptr);
2556     SetPerformingBiff(false);
2557   }
2558   aSpec->GetNumMessages(&m_numServerTotalMessages);
2559   aSpec->GetNumUnseenMessages(&m_numServerUnseenMessages);
2560   aSpec->GetNumRecentMessages(&m_numServerRecentMessages);
2561 
2562   // some servers don't return UIDNEXT on SELECT - don't crunch
2563   // existing values in that case.
2564   int32_t nextUID;
2565   aSpec->GetNextUID(&nextUID);
2566   if (nextUID != (int32_t)nsMsgKey_None) m_nextUID = nextUID;
2567 
2568   return rv;
2569 }
2570 
UpdateImapMailboxStatus(nsIImapProtocol * aProtocol,nsIMailboxSpec * aSpec)2571 NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxStatus(
2572     nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec) {
2573   NS_ENSURE_ARG_POINTER(aSpec);
2574   int32_t numUnread, numTotal;
2575   aSpec->GetNumUnseenMessages(&numUnread);
2576   aSpec->GetNumMessages(&numTotal);
2577   aSpec->GetNumRecentMessages(&m_numServerRecentMessages);
2578   int32_t prevNextUID = m_nextUID;
2579   aSpec->GetNextUID(&m_nextUID);
2580   bool summaryChanged = false;
2581 
2582   // If m_numServerUnseenMessages is 0, it means
2583   // this is the first time we've done a Status.
2584   // In that case, we count all the previous pending unread messages we know
2585   // about as unread messages. We may want to do similar things with total
2586   // messages, but the total messages include deleted messages if the folder
2587   // hasn't been expunged.
2588   int32_t previousUnreadMessages =
2589       (m_numServerUnseenMessages)
2590           ? m_numServerUnseenMessages
2591           : mNumPendingUnreadMessages + mNumUnreadMessages;
2592   if (numUnread != previousUnreadMessages || m_nextUID != prevNextUID) {
2593     int32_t unreadDelta =
2594         numUnread - (mNumPendingUnreadMessages + mNumUnreadMessages);
2595     if (numUnread - previousUnreadMessages != unreadDelta)
2596       NS_WARNING("unread count should match server count");
2597     ChangeNumPendingUnread(unreadDelta);
2598     if (unreadDelta > 0 &&
2599         !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
2600       SetHasNewMessages(true);
2601       SetNumNewMessages(unreadDelta);
2602       SetBiffState(nsMsgBiffState_NewMail);
2603     }
2604     summaryChanged = true;
2605   }
2606   SetPerformingBiff(false);
2607   if (m_numServerUnseenMessages != numUnread ||
2608       m_numServerTotalMessages != numTotal) {
2609     if (numUnread > m_numServerUnseenMessages ||
2610         m_numServerTotalMessages > numTotal)
2611       NotifyHasPendingMsgs();
2612     summaryChanged = true;
2613     m_numServerUnseenMessages = numUnread;
2614     m_numServerTotalMessages = numTotal;
2615   }
2616   if (summaryChanged) SummaryChanged();
2617 
2618   return NS_OK;
2619 }
2620 
ParseMsgHdrs(nsIImapProtocol * aProtocol,nsIImapHeaderXferInfo * aHdrXferInfo)2621 NS_IMETHODIMP nsImapMailFolder::ParseMsgHdrs(
2622     nsIImapProtocol* aProtocol, nsIImapHeaderXferInfo* aHdrXferInfo) {
2623   NS_ENSURE_ARG_POINTER(aHdrXferInfo);
2624   int32_t numHdrs;
2625   nsCOMPtr<nsIImapHeaderInfo> headerInfo;
2626   nsCOMPtr<nsIImapUrl> aImapUrl;
2627   nsImapAction imapAction = nsIImapUrl::nsImapTest;  // unused value.
2628   if (!mDatabase) GetDatabase();
2629 
2630   nsresult rv = aHdrXferInfo->GetNumHeaders(&numHdrs);
2631   if (aProtocol) {
2632     (void)aProtocol->GetRunningImapURL(getter_AddRefs(aImapUrl));
2633     if (aImapUrl) aImapUrl->GetImapAction(&imapAction);
2634   }
2635   for (uint32_t i = 0; NS_SUCCEEDED(rv) && (int32_t)i < numHdrs; i++) {
2636     rv = aHdrXferInfo->GetHeader(i, getter_AddRefs(headerInfo));
2637     NS_ENSURE_SUCCESS(rv, rv);
2638     if (!headerInfo) break;
2639     int32_t msgSize;
2640     nsMsgKey msgKey;
2641     bool containsKey;
2642     const char* msgHdrs;
2643     headerInfo->GetMsgSize(&msgSize);
2644     headerInfo->GetMsgUid(&msgKey);
2645     if (msgKey == nsMsgKey_None)  // not a valid uid.
2646       continue;
2647     if (imapAction == nsIImapUrl::nsImapMsgPreview) {
2648       nsCOMPtr<nsIMsgDBHdr> msgHdr;
2649       headerInfo->GetMsgHdrs(&msgHdrs);
2650       // create an input stream based on the hdr string.
2651       nsCOMPtr<nsIStringInputStream> inputStream =
2652           do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
2653       NS_ENSURE_SUCCESS(rv, rv);
2654       inputStream->ShareData(msgHdrs, strlen(msgHdrs));
2655       GetMessageHeader(msgKey, getter_AddRefs(msgHdr));
2656       if (msgHdr) {
2657         GetMsgPreviewTextFromStream(msgHdr, inputStream);
2658       }
2659       continue;
2660     }
2661     if (mDatabase &&
2662         NS_SUCCEEDED(mDatabase->ContainsKey(msgKey, &containsKey)) &&
2663         containsKey) {
2664       NS_ERROR("downloading hdrs for hdr we already have");
2665       continue;
2666     }
2667     nsresult rv = SetupHeaderParseStream(msgSize, EmptyCString(), nullptr);
2668     NS_ENSURE_SUCCESS(rv, rv);
2669     headerInfo->GetMsgHdrs(&msgHdrs);
2670     rv = ParseAdoptedHeaderLine(msgHdrs, msgKey);
2671     NS_ENSURE_SUCCESS(rv, rv);
2672     rv = NormalEndHeaderParseStream(aProtocol, aImapUrl);
2673   }
2674   return rv;
2675 }
2676 
SetupHeaderParseStream(uint32_t aSize,const nsACString & content_type,nsIMailboxSpec * boxSpec)2677 nsresult nsImapMailFolder::SetupHeaderParseStream(
2678     uint32_t aSize, const nsACString& content_type, nsIMailboxSpec* boxSpec) {
2679   if (!mDatabase) GetDatabase();
2680   m_nextMessageByteLength = aSize;
2681   if (!m_msgParser) {
2682     nsresult rv;
2683     m_msgParser = do_CreateInstance(kParseMailMsgStateCID, &rv);
2684     NS_ENSURE_SUCCESS(rv, rv);
2685   } else
2686     m_msgParser->Clear();
2687 
2688   m_msgParser->SetMailDB(mDatabase);
2689   if (mBackupDatabase) m_msgParser->SetBackupMailDB(mBackupDatabase);
2690   return m_msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
2691 }
2692 
ParseAdoptedHeaderLine(const char * aMessageLine,nsMsgKey aMsgKey)2693 nsresult nsImapMailFolder::ParseAdoptedHeaderLine(const char* aMessageLine,
2694                                                   nsMsgKey aMsgKey) {
2695   // we can get blocks that contain more than one line,
2696   // but they never contain partial lines
2697   const char* str = aMessageLine;
2698   m_curMsgUid = aMsgKey;
2699   m_msgParser->SetNewKey(m_curMsgUid);
2700   // m_envelope_pos, for local folders,
2701   // is the msg key. Setting this will set the msg key for the new header.
2702 
2703   int32_t len = strlen(str);
2704   char* currentEOL = PL_strstr(str, MSG_LINEBREAK);
2705   const char* currentLine = str;
2706   while (currentLine < (str + len)) {
2707     if (currentEOL) {
2708       m_msgParser->ParseAFolderLine(
2709           currentLine, (currentEOL + MSG_LINEBREAK_LEN) - currentLine);
2710       currentLine = currentEOL + MSG_LINEBREAK_LEN;
2711       currentEOL = PL_strstr(currentLine, MSG_LINEBREAK);
2712     } else {
2713       m_msgParser->ParseAFolderLine(currentLine, PL_strlen(currentLine));
2714       currentLine = str + len + 1;
2715     }
2716   }
2717   return NS_OK;
2718 }
2719 
NormalEndHeaderParseStream(nsIImapProtocol * aProtocol,nsIImapUrl * imapUrl)2720 nsresult nsImapMailFolder::NormalEndHeaderParseStream(
2721     nsIImapProtocol* aProtocol, nsIImapUrl* imapUrl) {
2722   nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
2723   nsresult rv;
2724   NS_ENSURE_TRUE(m_msgParser, NS_ERROR_NULL_POINTER);
2725 
2726   nsMailboxParseState parseState;
2727   m_msgParser->GetState(&parseState);
2728   if (parseState == nsIMsgParseMailMsgState::ParseHeadersState)
2729     m_msgParser->ParseAFolderLine(CRLF, 2);
2730   rv = m_msgParser->GetNewMsgHdr(getter_AddRefs(newMsgHdr));
2731   NS_ENSURE_SUCCESS(rv, rv);
2732 
2733   char* headers;
2734   int32_t headersSize;
2735 
2736   nsCOMPtr<nsIMsgWindow> msgWindow;
2737   nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
2738   if (imapUrl) {
2739     msgUrl = do_QueryInterface(imapUrl, &rv);
2740     NS_ENSURE_SUCCESS(rv, rv);
2741     msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
2742   }
2743 
2744   nsCOMPtr<nsIMsgIncomingServer> server;
2745   rv = GetServer(getter_AddRefs(server));
2746   NS_ENSURE_SUCCESS(rv, rv);
2747 
2748   nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
2749   rv = imapServer->GetIsGMailServer(&m_isGmailServer);
2750   NS_ENSURE_SUCCESS(rv, rv);
2751 
2752   newMsgHdr->SetMessageKey(m_curMsgUid);
2753   TweakHeaderFlags(aProtocol, newMsgHdr);
2754   uint32_t messageSize;
2755   if (NS_SUCCEEDED(newMsgHdr->GetMessageSize(&messageSize)))
2756     mFolderSize += messageSize;
2757   m_msgMovedByFilter = false;
2758 
2759   nsMsgKey highestUID = 0;
2760   nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
2761   if (mDatabase) mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
2762   if (dbFolderInfo)
2763     dbFolderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0,
2764                                     &highestUID);
2765 
2766   // If this is the inbox, try to apply filters. Otherwise, test the inherited
2767   // folder property "applyIncomingFilters" (which defaults to empty). If this
2768   // inherited property has the string value "true", then apply filters even
2769   // if this is not the Inbox folder.
2770   if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters) {
2771     // Use highwater to determine whether to filter?
2772     bool filterOnHighwater = false;
2773     nsCOMPtr<nsIPrefBranch> prefBranch(
2774         do_GetService(NS_PREFSERVICE_CONTRACTID));
2775     if (prefBranch)
2776       prefBranch->GetBoolPref("mail.imap.filter_on_new", &filterOnHighwater);
2777 
2778     uint32_t msgFlags;
2779     newMsgHdr->GetFlags(&msgFlags);
2780 
2781     // clang-format off
2782     bool doFilter = filterOnHighwater
2783       // Filter on largest UUID and not deleted.
2784       ? m_curMsgUid > highestUID && !(msgFlags & nsMsgMessageFlags::IMAPDeleted)
2785       // Filter on unread and not deleted.
2786       : !(msgFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted));
2787     // clang-format on
2788 
2789     if (doFilter)
2790       MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2791               ("(Imap) New message parsed, and filters will be run on it"));
2792     else
2793       MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2794               ("(Imap) New message parsed, but filters will not be run on it"));
2795 
2796     if (doFilter) {
2797       int32_t duplicateAction = nsIMsgIncomingServer::keepDups;
2798       if (server) server->GetIncomingDuplicateAction(&duplicateAction);
2799       if ((duplicateAction != nsIMsgIncomingServer::keepDups) &&
2800           mFlags & nsMsgFolderFlags::Inbox) {
2801         bool isDup;
2802         server->IsNewHdrDuplicate(newMsgHdr, &isDup);
2803         if (isDup) {
2804           // we want to do something similar to applying filter hits.
2805           // if a dup is marked read, it shouldn't trigger biff.
2806           // Same for deleting it or moving it to trash.
2807           switch (duplicateAction) {
2808             case nsIMsgIncomingServer::deleteDups: {
2809               uint32_t newFlags;
2810               newMsgHdr->OrFlags(
2811                   nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted,
2812                   &newFlags);
2813               StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
2814                              {m_curMsgUid}, nullptr);
2815               m_msgMovedByFilter = true;
2816             } break;
2817             case nsIMsgIncomingServer::moveDupsToTrash: {
2818               nsCOMPtr<nsIMsgFolder> trash;
2819               GetTrashFolder(getter_AddRefs(trash));
2820               if (trash) {
2821                 nsCString trashUri;
2822                 trash->GetURI(trashUri);
2823                 nsresult err = MoveIncorporatedMessage(
2824                     newMsgHdr, mDatabase, trashUri, nullptr, msgWindow);
2825                 if (NS_SUCCEEDED(err)) m_msgMovedByFilter = true;
2826               }
2827             } break;
2828             case nsIMsgIncomingServer::markDupsRead: {
2829               uint32_t newFlags;
2830               newMsgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags);
2831               StoreImapFlags(kImapMsgSeenFlag, true, {m_curMsgUid}, nullptr);
2832             } break;
2833           }
2834           int32_t numNewMessages;
2835           GetNumNewMessages(false, &numNewMessages);
2836           SetNumNewMessages(numNewMessages - 1);
2837         }
2838       }
2839       rv = m_msgParser->GetAllHeaders(&headers, &headersSize);
2840 
2841       if (NS_SUCCEEDED(rv) && headers && !m_msgMovedByFilter &&
2842           !m_filterListRequiresBody) {
2843         if (m_filterList) {
2844           GetMoveCoalescer();  // not sure why we're doing this here.
2845           MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
2846                   ("(Imap) ApplyFilterToHdr from "
2847                    "nsImapMailFolder::NormalEndHeaderParseStream()"));
2848           m_filterList->ApplyFiltersToHdr(
2849               nsMsgFilterType::InboxRule, newMsgHdr, this, mDatabase,
2850               nsDependentCSubstring(headers, headersSize), this, msgWindow);
2851           NotifyFolderEvent(kFiltersApplied);
2852         }
2853       }
2854     }
2855   }
2856   // here we need to tweak flags from uid state..
2857   if (mDatabase && (!m_msgMovedByFilter || ShowDeletedMessages())) {
2858     nsCOMPtr<nsIMsgFolderNotificationService> notifier(
2859         do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
2860     // Check if this header corresponds to a pseudo header
2861     // we have from doing a pseudo-offline move and then downloading
2862     // the real header from the server. In that case, we notify
2863     // db/folder listeners that the pseudo-header has become the new
2864     // header, i.e., the key has changed.
2865     nsCString newMessageId;
2866     newMsgHdr->GetMessageId(getter_Copies(newMessageId));
2867     nsMsgKey pseudoKey =
2868         m_pseudoHdrs.MaybeGet(newMessageId).valueOr(nsMsgKey_None);
2869     if (notifier && pseudoKey != nsMsgKey_None) {
2870       notifier->NotifyMsgKeyChanged(pseudoKey, newMsgHdr);
2871       m_pseudoHdrs.Remove(newMessageId);
2872     }
2873     mDatabase->AddNewHdrToDB(newMsgHdr, true);
2874     if (notifier) notifier->NotifyMsgAdded(newMsgHdr);
2875     // mark the header as not yet reported classified
2876     OrProcessingFlags(m_curMsgUid, nsMsgProcessingFlags::NotReportedClassified);
2877   }
2878   // adjust highestRecordedUID
2879   if (dbFolderInfo) {
2880     if (m_curMsgUid > highestUID) {
2881       MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
2882               ("NormalEndHeaderParseStream(): Store new highest UID=%" PRIu32
2883                " for folder=%s",
2884                m_curMsgUid, m_onlineFolderName.get()));
2885       dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName,
2886                                       m_curMsgUid);
2887     }
2888   }
2889 
2890   if (m_isGmailServer) {
2891     nsCOMPtr<nsIImapFlagAndUidState> flagState;
2892     aProtocol->GetFlagAndUidState(getter_AddRefs(flagState));
2893     nsCString msgIDValue;
2894     nsCString threadIDValue;
2895     nsCString labelsValue;
2896     flagState->GetCustomAttribute(m_curMsgUid, "X-GM-MSGID"_ns, msgIDValue);
2897     flagState->GetCustomAttribute(m_curMsgUid, "X-GM-THRID"_ns, threadIDValue);
2898     flagState->GetCustomAttribute(m_curMsgUid, "X-GM-LABELS"_ns, labelsValue);
2899     newMsgHdr->SetStringProperty("X-GM-MSGID", msgIDValue.get());
2900     newMsgHdr->SetStringProperty("X-GM-THRID", threadIDValue.get());
2901     newMsgHdr->SetStringProperty("X-GM-LABELS", labelsValue.get());
2902   }
2903 
2904   m_msgParser->Clear();  // clear out parser, because it holds onto a msg hdr.
2905   m_msgParser->SetMailDB(nullptr);  // tell it to let go of the db too.
2906   // I don't think we want to do this - it does bad things like set the size
2907   // incorrectly.
2908   //    m_msgParser->FinishHeader();
2909   return NS_OK;
2910 }
2911 
AbortHeaderParseStream(nsIImapProtocol * aProtocol)2912 NS_IMETHODIMP nsImapMailFolder::AbortHeaderParseStream(
2913     nsIImapProtocol* aProtocol) {
2914   nsresult rv = NS_ERROR_FAILURE;
2915   return rv;
2916 }
2917 
BeginCopy(nsIMsgDBHdr * message)2918 NS_IMETHODIMP nsImapMailFolder::BeginCopy(nsIMsgDBHdr* message) {
2919   NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER);
2920   nsresult rv;
2921   if (m_copyState->m_tmpFile)  // leftover file spec nuke it
2922   {
2923     rv = m_copyState->m_tmpFile->Remove(false);
2924     if (NS_FAILED(rv)) {
2925       nsCString nativePath = m_copyState->m_tmpFile->HumanReadablePath();
2926       MOZ_LOG(IMAP, mozilla::LogLevel::Info,
2927               ("couldn't remove prev temp file %s: %" PRIx32, nativePath.get(),
2928                static_cast<uint32_t>(rv)));
2929     }
2930     m_copyState->m_tmpFile = nullptr;
2931   }
2932 
2933   rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "nscpmsg.txt",
2934                                        getter_AddRefs(m_copyState->m_tmpFile));
2935   if (NS_FAILED(rv))
2936     MOZ_LOG(IMAP, mozilla::LogLevel::Info,
2937             ("couldn't find nscpmsg.txt:%" PRIx32, static_cast<uint32_t>(rv)));
2938   NS_ENSURE_SUCCESS(rv, rv);
2939 
2940   // create a unique file, since multiple copies may be open on multiple folders
2941   rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
2942   if (NS_FAILED(rv)) {
2943     MOZ_LOG(IMAP, mozilla::LogLevel::Info,
2944             ("couldn't create temp nscpmsg.txt:%" PRIx32,
2945              static_cast<uint32_t>(rv)));
2946     // Last ditch attempt to create a temp file, because virus checker might
2947     // be locking the previous temp file, and CreateUnique fails if the file
2948     // is locked. Use the message key to make a unique name.
2949     if (message) {
2950       nsCString tmpFileName("nscpmsg-");
2951       nsMsgKey msgKey;
2952       message->GetMessageKey(&msgKey);
2953       tmpFileName.AppendInt(msgKey);
2954       tmpFileName.AppendLiteral(".txt");
2955       m_copyState->m_tmpFile->SetNativeLeafName(tmpFileName);
2956       rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE,
2957                                                 00600);
2958       if (NS_FAILED(rv)) {
2959         MOZ_LOG(IMAP, mozilla::LogLevel::Info,
2960                 ("couldn't create temp nscpmsg.txt: %" PRIx32,
2961                  static_cast<uint32_t>(rv)));
2962         OnCopyCompleted(m_copyState->m_srcSupport, rv);
2963         return rv;
2964       }
2965     }
2966   }
2967 
2968   nsCOMPtr<nsIOutputStream> fileOutputStream;
2969   rv = MsgNewBufferedFileOutputStream(
2970       getter_AddRefs(m_copyState->m_msgFileStream), m_copyState->m_tmpFile, -1,
2971       00600);
2972   if (NS_FAILED(rv))
2973     MOZ_LOG(IMAP, mozilla::LogLevel::Info,
2974             ("couldn't create output file stream: %" PRIx32,
2975              static_cast<uint32_t>(rv)));
2976 
2977   if (!m_copyState->m_dataBuffer)
2978     m_copyState->m_dataBuffer = (char*)PR_CALLOC(COPY_BUFFER_SIZE + 1);
2979   NS_ENSURE_TRUE(m_copyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY);
2980   m_copyState->m_dataBufferSize = COPY_BUFFER_SIZE;
2981   return NS_OK;
2982 }
2983 
CopyDataToOutputStreamForAppend(nsIInputStream * aIStream,int32_t aLength,nsIOutputStream * outputStream)2984 NS_IMETHODIMP nsImapMailFolder::CopyDataToOutputStreamForAppend(
2985     nsIInputStream* aIStream, int32_t aLength, nsIOutputStream* outputStream) {
2986   uint32_t readCount;
2987   uint32_t writeCount;
2988   if (!m_copyState) m_copyState = new nsImapMailCopyState();
2989 
2990   if (aLength + m_copyState->m_leftOver > m_copyState->m_dataBufferSize) {
2991     char* newBuffer = (char*)PR_REALLOC(m_copyState->m_dataBuffer,
2992                                         aLength + m_copyState->m_leftOver + 1);
2993     NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
2994     m_copyState->m_dataBuffer = newBuffer;
2995     m_copyState->m_dataBufferSize = aLength + m_copyState->m_leftOver;
2996   }
2997 
2998   char *start, *end;
2999   uint32_t linebreak_len = 1;
3000 
3001   nsresult rv = aIStream->Read(
3002       m_copyState->m_dataBuffer + m_copyState->m_leftOver, aLength, &readCount);
3003   if (NS_FAILED(rv)) return rv;
3004 
3005   m_copyState->m_leftOver += readCount;
3006   m_copyState->m_dataBuffer[m_copyState->m_leftOver] = '\0';
3007 
3008   start = m_copyState->m_dataBuffer;
3009   if (m_copyState->m_eatLF) {
3010     if (*start == '\n') start++;
3011     m_copyState->m_eatLF = false;
3012   }
3013   end = PL_strpbrk(start, "\r\n");
3014   if (end && *end == '\r' && *(end + 1) == '\n') linebreak_len = 2;
3015 
3016   while (start && end) {
3017     if (PL_strncasecmp(start, "X-Mozilla-Status:", 17) &&
3018         PL_strncasecmp(start, "X-Mozilla-Status2:", 18) &&
3019         PL_strncmp(start, "From - ", 7)) {
3020       rv = outputStream->Write(start, end - start, &writeCount);
3021       rv = outputStream->Write(CRLF, 2, &writeCount);
3022     }
3023     start = end + linebreak_len;
3024     if (start >= m_copyState->m_dataBuffer + m_copyState->m_leftOver) {
3025       m_copyState->m_leftOver = 0;
3026       break;
3027     }
3028     linebreak_len = 1;
3029 
3030     end = PL_strpbrk(start, "\r\n");
3031     if (end && *end == '\r') {
3032       if (*(end + 1) == '\n')
3033         linebreak_len = 2;
3034       else if (!*(end + 1))  // block might have split CRLF so remember if
3035         m_copyState->m_eatLF = true;  // we should eat LF
3036     }
3037 
3038     if (start && !end) {
3039       m_copyState->m_leftOver -= (start - m_copyState->m_dataBuffer);
3040       memcpy(m_copyState->m_dataBuffer, start,
3041              m_copyState->m_leftOver + 1);  // including null
3042     }
3043   }
3044   return rv;
3045 }
3046 
CopyDataDone()3047 NS_IMETHODIMP nsImapMailFolder::CopyDataDone() {
3048   m_copyState = nullptr;
3049   return NS_OK;
3050 }
3051 
3052 // sICopyMessageListener methods, BeginCopy, CopyData, EndCopy, EndMove,
3053 // StartMessage, EndMessage
CopyData(nsIInputStream * aIStream,int32_t aLength)3054 NS_IMETHODIMP nsImapMailFolder::CopyData(nsIInputStream* aIStream,
3055                                          int32_t aLength) {
3056   NS_ENSURE_TRUE(
3057       m_copyState && m_copyState->m_msgFileStream && m_copyState->m_dataBuffer,
3058       NS_ERROR_NULL_POINTER);
3059   nsresult rv = CopyDataToOutputStreamForAppend(aIStream, aLength,
3060                                                 m_copyState->m_msgFileStream);
3061   if (NS_FAILED(rv)) {
3062     MOZ_LOG(IMAP, mozilla::LogLevel::Info,
3063             ("CopyData failed: %" PRIx32, static_cast<uint32_t>(rv)));
3064     OnCopyCompleted(m_copyState->m_srcSupport, rv);
3065   }
3066   return rv;
3067 }
3068 
EndCopy(bool copySucceeded)3069 NS_IMETHODIMP nsImapMailFolder::EndCopy(bool copySucceeded) {
3070   nsresult rv = copySucceeded ? NS_OK : NS_ERROR_FAILURE;
3071   if (copySucceeded && m_copyState && m_copyState->m_msgFileStream) {
3072     nsCOMPtr<nsIUrlListener> urlListener;
3073     m_copyState->m_msgFileStream->Close();
3074     // m_tmpFile can be stale because we wrote to it
3075     nsCOMPtr<nsIFile> tmpFile;
3076     m_copyState->m_tmpFile->Clone(getter_AddRefs(tmpFile));
3077     m_copyState->m_tmpFile = tmpFile;
3078     nsCOMPtr<nsIImapService> imapService =
3079         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
3080     NS_ENSURE_SUCCESS(rv, rv);
3081 
3082     rv =
3083         QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
3084     rv = imapService->AppendMessageFromFile(
3085         m_copyState->m_tmpFile, this, EmptyCString(), true,
3086         m_copyState->m_selectedState, urlListener, nullptr, m_copyState,
3087         m_copyState->m_msgWindow);
3088   }
3089   if (NS_FAILED(rv) || !copySucceeded)
3090     MOZ_LOG(IMAP, mozilla::LogLevel::Info,
3091             ("EndCopy failed: %" PRIx32, static_cast<uint32_t>(rv)));
3092   return rv;
3093 }
3094 
EndMove(bool moveSucceeded)3095 NS_IMETHODIMP nsImapMailFolder::EndMove(bool moveSucceeded) { return NS_OK; }
3096 // this is the beginning of the next message copied
StartMessage()3097 NS_IMETHODIMP nsImapMailFolder::StartMessage() { return NS_OK; }
3098 
3099 // just finished the current message.
EndMessage(nsMsgKey key)3100 NS_IMETHODIMP nsImapMailFolder::EndMessage(nsMsgKey key) { return NS_OK; }
3101 
ApplyFilterHit(nsIMsgFilter * filter,nsIMsgWindow * msgWindow,bool * applyMore)3102 NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter* filter,
3103                                                nsIMsgWindow* msgWindow,
3104                                                bool* applyMore) {
3105   //
3106   //  This routine is called indirectly from ApplyFiltersToHdr in two
3107   //  circumstances, controlled by m_filterListRequiresBody:
3108   //
3109   //  If false, after headers are parsed in NormalEndHeaderParseStream.
3110   //  If true, after the message body is downloaded in NormalEndMsgWriteStream.
3111   //
3112   //  In NormalEndHeaderParseStream, the message has not been added to the
3113   //  database, and it is important that database notifications and count
3114   //  updates do not occur. In NormalEndMsgWriteStream, the message has been
3115   //  added to the database, and database notifications and count updates
3116   //  should be performed.
3117   //
3118 
3119   NS_ENSURE_ARG_POINTER(filter);
3120   NS_ENSURE_ARG_POINTER(applyMore);
3121 
3122   nsresult rv = NS_OK;
3123 
3124   nsCOMPtr<nsIMsgDBHdr> msgHdr;
3125   if (m_filterListRequiresBody)
3126     GetMessageHeader(m_curMsgUid, getter_AddRefs(msgHdr));
3127   else if (m_msgParser)
3128     m_msgParser->GetNewMsgHdr(getter_AddRefs(msgHdr));
3129   NS_ENSURE_TRUE(msgHdr,
3130                  NS_ERROR_NULL_POINTER);  // fatal error, cannot apply filters
3131 
3132   bool deleteToTrash = DeleteIsMoveToTrash();
3133 
3134   nsTArray<RefPtr<nsIMsgRuleAction>> filterActionList;
3135   rv = filter->GetSortedActionList(filterActionList);
3136   NS_ENSURE_SUCCESS(rv, rv);
3137 
3138   uint32_t numActions = filterActionList.Length();
3139 
3140   nsCString msgId;
3141   msgHdr->GetMessageId(getter_Copies(msgId));
3142   nsMsgKey msgKey;
3143   msgHdr->GetMessageKey(&msgKey);
3144   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
3145           ("(Imap) Applying %" PRIu32
3146            " filter actions on message with key %" PRIu32,
3147            numActions, msgKeyToInt(msgKey)));
3148   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
3149           ("(Imap) Message ID: %s", msgId.get()));
3150 
3151   bool loggingEnabled = false;
3152   if (m_filterList && numActions)
3153     (void)m_filterList->GetLoggingEnabled(&loggingEnabled);
3154 
3155   bool msgIsNew = true;
3156 
3157   rv = GetDatabase();
3158   NS_ENSURE_SUCCESS(rv, rv);
3159 
3160   nsresult finalResult = NS_OK;  // result of all actions
3161   for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++) {
3162     nsCOMPtr<nsIMsgRuleAction> filterAction(filterActionList[actionIndex]);
3163     if (!filterAction) {
3164       MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
3165               ("(Imap) Filter action at index %" PRIu32 " invalid, skipping",
3166                actionIndex));
3167       continue;
3168     }
3169 
3170     rv = NS_OK;  // result of the current action
3171     nsMsgRuleActionType actionType;
3172     if (NS_SUCCEEDED(filterAction->GetType(&actionType))) {
3173       MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
3174               ("(Imap) Running filter action at index %" PRIu32
3175                ", action type = %i",
3176                actionIndex, actionType));
3177       if (loggingEnabled) (void)filter->LogRuleHit(filterAction, msgHdr);
3178 
3179       nsCString actionTargetFolderUri;
3180       if (actionType == nsMsgFilterAction::MoveToFolder ||
3181           actionType == nsMsgFilterAction::CopyToFolder) {
3182         rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
3183         if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty()) {
3184           // clang-format off
3185           MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
3186                   ("(Imap) Target URI for Copy/Move action is empty, skipping"));
3187           // clang-format on
3188           NS_ASSERTION(false, "actionTargetFolderUri is empty");
3189           continue;
3190         }
3191       }
3192 
3193       uint32_t msgFlags;
3194       msgHdr->GetFlags(&msgFlags);
3195       bool isRead = (msgFlags & nsMsgMessageFlags::Read);
3196 
3197       switch (actionType) {
3198         case nsMsgFilterAction::Delete: {
3199           if (deleteToTrash) {
3200             // set value to trash folder
3201             nsCOMPtr<nsIMsgFolder> mailTrash;
3202             rv = GetTrashFolder(getter_AddRefs(mailTrash));
3203             if (NS_SUCCEEDED(rv) && mailTrash) {
3204               rv = mailTrash->GetURI(actionTargetFolderUri);
3205               if (NS_FAILED(rv)) break;
3206             }
3207             // msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags);  // mark
3208             // read in trash.
3209           } else {
3210             mDatabase->MarkHdrRead(msgHdr, true, nullptr);
3211             mDatabase->MarkImapDeleted(msgKey, true, nullptr);
3212             rv = StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
3213                                 {msgKey}, nullptr);
3214             if (NS_FAILED(rv)) break;
3215             // this will prevent us from adding the header to the db.
3216             m_msgMovedByFilter = true;
3217           }
3218           msgIsNew = false;
3219         }
3220           // note that delete falls through to move.
3221           [[fallthrough]];
3222         case nsMsgFilterAction::MoveToFolder: {
3223           // if moving to a different file, do it.
3224           nsCString uri;
3225           rv = GetURI(uri);
3226           if (NS_FAILED(rv)) break;
3227 
3228           if (!actionTargetFolderUri.Equals(uri)) {
3229             msgHdr->GetFlags(&msgFlags);
3230             if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) {
3231               mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
3232               mDatabase->MarkMDNSent(msgKey, true, nullptr);
3233             }
3234             nsresult rv = MoveIncorporatedMessage(
3235                 msgHdr, mDatabase, actionTargetFolderUri, filter, msgWindow);
3236             if (NS_SUCCEEDED(rv)) {
3237               m_msgMovedByFilter = true;
3238             } else {
3239               if (loggingEnabled) {
3240                 (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
3241                                              "filterFailureMoveFailed"_ns);
3242               }
3243             }
3244           }
3245           // don't apply any more filters, even if it was a move to the same
3246           // folder
3247           *applyMore = false;
3248         } break;
3249         case nsMsgFilterAction::CopyToFolder: {
3250           nsCString uri;
3251           rv = GetURI(uri);
3252           if (NS_FAILED(rv)) break;
3253 
3254           if (!actionTargetFolderUri.Equals(uri)) {
3255             // XXXshaver I'm not actually 100% what the right semantics are for
3256             // MDNs and copied messages, but I suspect deep down inside that
3257             // we probably want to suppress them only on the copies.
3258             msgHdr->GetFlags(&msgFlags);
3259             if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) {
3260               mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
3261               mDatabase->MarkMDNSent(msgKey, true, nullptr);
3262             }
3263 
3264             nsCOMPtr<nsIMsgFolder> dstFolder;
3265             rv = GetExistingFolder(actionTargetFolderUri,
3266                                    getter_AddRefs(dstFolder));
3267             if (NS_FAILED(rv)) break;
3268 
3269             nsCOMPtr<nsIMsgCopyService> copyService =
3270                 do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
3271             if (NS_FAILED(rv)) break;
3272             rv = copyService->CopyMessages(this, {&*msgHdr}, dstFolder, false,
3273                                            nullptr, msgWindow, false);
3274             if (NS_FAILED(rv)) {
3275               if (loggingEnabled) {
3276                 (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
3277                                              "filterFailureCopyFailed"_ns);
3278               }
3279             }
3280           }
3281         } break;
3282         case nsMsgFilterAction::MarkRead: {
3283           mDatabase->MarkHdrRead(msgHdr, true, nullptr);
3284           rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
3285           msgIsNew = false;
3286         } break;
3287         case nsMsgFilterAction::MarkUnread: {
3288           mDatabase->MarkHdrRead(msgHdr, false, nullptr);
3289           rv = StoreImapFlags(kImapMsgSeenFlag, false, {msgKey}, nullptr);
3290           msgIsNew = true;
3291         } break;
3292         case nsMsgFilterAction::MarkFlagged: {
3293           mDatabase->MarkHdrMarked(msgHdr, true, nullptr);
3294           rv = StoreImapFlags(kImapMsgFlaggedFlag, true, {msgKey}, nullptr);
3295         } break;
3296         case nsMsgFilterAction::KillThread:
3297         case nsMsgFilterAction::WatchThread: {
3298           nsCOMPtr<nsIMsgThread> msgThread;
3299           nsMsgKey threadKey;
3300           mDatabase->GetThreadContainingMsgHdr(msgHdr,
3301                                                getter_AddRefs(msgThread));
3302           if (msgThread) {
3303             msgThread->GetThreadKey(&threadKey);
3304             if (actionType == nsMsgFilterAction::KillThread)
3305               rv = mDatabase->MarkThreadIgnored(msgThread, threadKey, true,
3306                                                 nullptr);
3307             else
3308               rv = mDatabase->MarkThreadWatched(msgThread, threadKey, true,
3309                                                 nullptr);
3310           } else {
3311             if (actionType == nsMsgFilterAction::KillThread)
3312               rv = msgHdr->SetUint32Property("ProtoThreadFlags",
3313                                              nsMsgMessageFlags::Ignored);
3314             else
3315               rv = msgHdr->SetUint32Property("ProtoThreadFlags",
3316                                              nsMsgMessageFlags::Watched);
3317           }
3318           if (actionType == nsMsgFilterAction::KillThread) {
3319             mDatabase->MarkHdrRead(msgHdr, true, nullptr);
3320             rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
3321             msgIsNew = false;
3322           }
3323         } break;
3324         case nsMsgFilterAction::KillSubthread: {
3325           mDatabase->MarkHeaderKilled(msgHdr, true, nullptr);
3326           mDatabase->MarkHdrRead(msgHdr, true, nullptr);
3327           rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
3328           msgIsNew = false;
3329         } break;
3330         case nsMsgFilterAction::ChangePriority: {
3331           nsMsgPriorityValue filterPriority;  // a int32_t
3332           filterAction->GetPriority(&filterPriority);
3333           rv = mDatabase->SetUint32PropertyByHdr(
3334               msgHdr, "priority", static_cast<uint32_t>(filterPriority));
3335         } break;
3336         case nsMsgFilterAction::Label: {
3337           nsMsgLabelValue filterLabel;
3338           filterAction->GetLabel(&filterLabel);
3339           mDatabase->SetUint32PropertyByHdr(msgHdr, "label",
3340                                             static_cast<uint32_t>(filterLabel));
3341           rv = StoreImapFlags((filterLabel << 9), true, {msgKey}, nullptr);
3342         } break;
3343         case nsMsgFilterAction::AddTag: {
3344           nsCString keyword;
3345           filterAction->GetStrValue(keyword);
3346           rv = AddKeywordsToMessages({&*msgHdr}, keyword);
3347         } break;
3348         case nsMsgFilterAction::JunkScore: {
3349           nsAutoCString junkScoreStr;
3350           int32_t junkScore;
3351           filterAction->GetJunkScore(&junkScore);
3352           junkScoreStr.AppendInt(junkScore);
3353           rv = mDatabase->SetStringProperty(msgKey, "junkscore",
3354                                             junkScoreStr.get());
3355           mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter");
3356 
3357           // If score is available, set up to store junk status on server.
3358           if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE ||
3359               junkScore == nsIJunkMailPlugin::IS_HAM_SCORE) {
3360             nsTArray<nsMsgKey>* keysToClassify = m_moveCoalescer->GetKeyBucket(
3361                 (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) ? 0 : 1);
3362             NS_ASSERTION(keysToClassify, "error getting key bucket");
3363             if (keysToClassify) keysToClassify->AppendElement(msgKey);
3364             if (msgIsNew && junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) {
3365               msgIsNew = false;
3366               mDatabase->MarkHdrNotNew(msgHdr, nullptr);
3367               // nsMsgDBFolder::SendFlagNotifications by the call to
3368               // SetBiffState(nsMsgBiffState_NoMail) will reset numNewMessages
3369               // only if the message is also read and database notifications
3370               // are active, but we are not going to mark it read in this
3371               // action, preferring to leave the choice to the user.
3372               // So correct numNewMessages.
3373               if (m_filterListRequiresBody) {
3374                 msgHdr->GetFlags(&msgFlags);
3375                 if (!(msgFlags & nsMsgMessageFlags::Read)) {
3376                   int32_t numNewMessages;
3377                   GetNumNewMessages(false, &numNewMessages);
3378                   SetNumNewMessages(--numNewMessages);
3379                   SetHasNewMessages(numNewMessages != 0);
3380                 }
3381               }
3382             }
3383           }
3384         } break;
3385         case nsMsgFilterAction::Forward: {
3386           nsCString forwardTo;
3387           filterAction->GetStrValue(forwardTo);
3388           nsCOMPtr<nsIMsgIncomingServer> server;
3389           rv = GetServer(getter_AddRefs(server));
3390           if (NS_FAILED(rv)) break;
3391           if (!forwardTo.IsEmpty()) {
3392             nsCOMPtr<nsIMsgComposeService> compService =
3393                 do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
3394             if (NS_FAILED(rv)) break;
3395             rv = compService->ForwardMessage(
3396                 NS_ConvertUTF8toUTF16(forwardTo), msgHdr, msgWindow, server,
3397                 nsIMsgComposeService::kForwardAsDefault);
3398           }
3399         } break;
3400 
3401         case nsMsgFilterAction::Reply: {
3402           nsCString replyTemplateUri;
3403           filterAction->GetStrValue(replyTemplateUri);
3404           nsCOMPtr<nsIMsgIncomingServer> server;
3405           rv = GetServer(getter_AddRefs(server));
3406           if (NS_FAILED(rv)) break;
3407           if (!replyTemplateUri.IsEmpty()) {
3408             nsCOMPtr<nsIMsgComposeService> compService =
3409                 do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
3410             if (NS_SUCCEEDED(rv) && compService) {
3411               rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri,
3412                                                   msgWindow, server);
3413               if (NS_FAILED(rv)) {
3414                 NS_WARNING("ReplyWithTemplate failed");
3415                 if (rv == NS_ERROR_ABORT) {
3416                   (void)filter->LogRuleHitFail(
3417                       filterAction, msgHdr, rv,
3418                       "filterFailureSendingReplyAborted"_ns);
3419                 } else {
3420                   (void)filter->LogRuleHitFail(
3421                       filterAction, msgHdr, rv,
3422                       "filterFailureSendingReplyError"_ns);
3423                 }
3424               }
3425             }
3426           }
3427         } break;
3428 
3429         case nsMsgFilterAction::StopExecution: {
3430           // don't apply any more filters
3431           *applyMore = false;
3432           rv = NS_OK;
3433         } break;
3434 
3435         case nsMsgFilterAction::Custom: {
3436           nsCOMPtr<nsIMsgFilterCustomAction> customAction;
3437           rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
3438           if (NS_FAILED(rv)) break;
3439 
3440           nsAutoCString value;
3441           rv = filterAction->GetStrValue(value);
3442           if (NS_FAILED(rv)) break;
3443 
3444           rv = customAction->ApplyAction({&*msgHdr}, value, nullptr,
3445                                          nsMsgFilterType::InboxRule, msgWindow);
3446           // allow custom action to affect new
3447           msgHdr->GetFlags(&msgFlags);
3448           if (!(msgFlags & nsMsgMessageFlags::New)) msgIsNew = false;
3449         } break;
3450 
3451         default:
3452           NS_ERROR("unexpected filter action");
3453           rv = NS_ERROR_UNEXPECTED;
3454           break;
3455       }
3456     }
3457     if (NS_FAILED(rv)) {
3458       finalResult = rv;
3459       MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
3460               ("(Imap) Action execution failed with error: %" PRIx32,
3461                static_cast<uint32_t>(rv)));
3462       if (loggingEnabled) {
3463         (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
3464                                      "filterFailureAction"_ns);
3465       }
3466     } else {
3467       MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
3468               ("(Imap) Action execution succeeded"));
3469     }
3470   }
3471   if (!msgIsNew) {
3472     int32_t numNewMessages;
3473     GetNumNewMessages(false, &numNewMessages);
3474     // When database notifications are active, new counts will be reset
3475     // to zero in nsMsgDBFolder::SendFlagNotifications by the call to
3476     // SetBiffState(nsMsgBiffState_NoMail), so don't repeat them here.
3477     if (!m_filterListRequiresBody) SetNumNewMessages(--numNewMessages);
3478     if (mDatabase) mDatabase->MarkHdrNotNew(msgHdr, nullptr);
3479     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
3480             ("(Imap) Message will not be marked new"));
3481   }
3482   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
3483           ("(Imap) Finished executing actions"));
3484   return finalResult;
3485 }
3486 
SetImapFlags(const char * uids,int32_t flags,nsIURI ** url)3487 NS_IMETHODIMP nsImapMailFolder::SetImapFlags(const char* uids, int32_t flags,
3488                                              nsIURI** url) {
3489   nsresult rv;
3490   nsCOMPtr<nsIImapService> imapService =
3491       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
3492   NS_ENSURE_SUCCESS(rv, rv);
3493 
3494   return imapService->SetMessageFlags(this, this, url, nsAutoCString(uids),
3495                                       flags, true);
3496 }
3497 
3498 // "this" is the parent folder
PlaybackOfflineFolderCreate(const nsAString & aFolderName,nsIMsgWindow * aWindow,nsIURI ** url)3499 NS_IMETHODIMP nsImapMailFolder::PlaybackOfflineFolderCreate(
3500     const nsAString& aFolderName, nsIMsgWindow* aWindow, nsIURI** url) {
3501   nsresult rv;
3502   nsCOMPtr<nsIImapService> imapService =
3503       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
3504   NS_ENSURE_SUCCESS(rv, rv);
3505   return imapService->CreateFolder(this, aFolderName, this, url);
3506 }
3507 
3508 NS_IMETHODIMP
ReplayOfflineMoveCopy(const nsTArray<nsMsgKey> & aMsgKeys,bool isMove,nsIMsgFolder * aDstFolder,nsIUrlListener * aUrlListener,nsIMsgWindow * aWindow)3509 nsImapMailFolder::ReplayOfflineMoveCopy(const nsTArray<nsMsgKey>& aMsgKeys,
3510                                         bool isMove, nsIMsgFolder* aDstFolder,
3511                                         nsIUrlListener* aUrlListener,
3512                                         nsIMsgWindow* aWindow) {
3513   nsresult rv;
3514 
3515   nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aDstFolder);
3516   if (imapFolder) {
3517     nsImapMailFolder* destImapFolder =
3518         static_cast<nsImapMailFolder*>(aDstFolder);
3519     nsCOMPtr<nsIMsgDatabase> dstFolderDB;
3520     aDstFolder->GetMsgDatabase(getter_AddRefs(dstFolderDB));
3521     if (dstFolderDB) {
3522       // find the fake header in the destination db, and use that to
3523       // set the pending attributes on the real headers. To do this,
3524       // we need to iterate over the offline ops in the destination db,
3525       // looking for ones with matching keys and source folder uri.
3526       // If we find that offline op, its "key" will be the key of the fake
3527       // header, so we just need to get the header for that key
3528       // from the dest db.
3529       nsTArray<nsMsgKey> offlineOps;
3530       if (NS_SUCCEEDED(dstFolderDB->ListAllOfflineOpIds(&offlineOps))) {
3531         nsTArray<RefPtr<nsIMsgDBHdr>> messages;
3532         nsCString srcFolderUri;
3533         GetURI(srcFolderUri);
3534         nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
3535         for (uint32_t opIndex = 0; opIndex < offlineOps.Length(); opIndex++) {
3536           dstFolderDB->GetOfflineOpForKey(offlineOps[opIndex], false,
3537                                           getter_AddRefs(currentOp));
3538           if (currentOp) {
3539             nsCString opSrcUri;
3540             currentOp->GetSourceFolderURI(opSrcUri);
3541             if (opSrcUri.Equals(srcFolderUri)) {
3542               nsMsgKey srcMessageKey;
3543               currentOp->GetSrcMessageKey(&srcMessageKey);
3544               for (auto key : aMsgKeys) {
3545                 if (srcMessageKey == key) {
3546                   nsCOMPtr<nsIMsgDBHdr> fakeDestHdr;
3547                   dstFolderDB->GetMsgHdrForKey(offlineOps[opIndex],
3548                                                getter_AddRefs(fakeDestHdr));
3549                   if (fakeDestHdr) messages.AppendElement(fakeDestHdr);
3550                   break;
3551                 }
3552               }
3553             }
3554           }
3555         }
3556         // 3rd parameter: Set offline flag.
3557         destImapFolder->SetPendingAttributes(messages, isMove, true);
3558       }
3559     }
3560     // if we can't get the dst folder db, we should still try to playback
3561     // the offline move/copy.
3562   }
3563 
3564   nsCOMPtr<nsIImapService> imapService =
3565       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
3566   NS_ENSURE_SUCCESS(rv, rv);
3567   nsCOMPtr<nsIURI> resultUrl;
3568   nsAutoCString uids;
3569   AllocateUidStringFromKeys(aMsgKeys, uids);
3570   rv = imapService->OnlineMessageCopy(this, uids, aDstFolder, true, isMove,
3571                                       aUrlListener, getter_AddRefs(resultUrl),
3572                                       nullptr, aWindow);
3573   if (resultUrl) {
3574     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(resultUrl, &rv);
3575     NS_ENSURE_SUCCESS(rv, rv);
3576     nsCOMPtr<nsIUrlListener> folderListener = do_QueryInterface(aDstFolder);
3577     if (folderListener) mailnewsUrl->RegisterListener(folderListener);
3578   }
3579   return rv;
3580 }
3581 
AddMoveResultPseudoKey(nsMsgKey aMsgKey)3582 NS_IMETHODIMP nsImapMailFolder::AddMoveResultPseudoKey(nsMsgKey aMsgKey) {
3583   nsresult rv = GetDatabase();
3584   NS_ENSURE_SUCCESS(rv, rv);
3585 
3586   nsCOMPtr<nsIMsgDBHdr> pseudoHdr;
3587   rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(pseudoHdr));
3588   NS_ENSURE_SUCCESS(rv, rv);
3589   nsCString messageId;
3590   pseudoHdr->GetMessageId(getter_Copies(messageId));
3591   // err on the side of caution and ignore messages w/o messageid.
3592   if (messageId.IsEmpty()) return NS_OK;
3593   m_pseudoHdrs.InsertOrUpdate(messageId, aMsgKey);
3594   return NS_OK;
3595 }
3596 
StoreImapFlags(int32_t flags,bool addFlags,const nsTArray<nsMsgKey> & keys,nsIUrlListener * aUrlListener)3597 NS_IMETHODIMP nsImapMailFolder::StoreImapFlags(int32_t flags, bool addFlags,
3598                                                const nsTArray<nsMsgKey>& keys,
3599                                                nsIUrlListener* aUrlListener) {
3600   nsresult rv;
3601   if (!WeAreOffline()) {
3602     nsCOMPtr<nsIImapService> imapService =
3603         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
3604     NS_ENSURE_SUCCESS(rv, rv);
3605     nsAutoCString msgIds;
3606     AllocateUidStringFromKeys(keys, msgIds);
3607     if (addFlags)
3608       imapService->AddMessageFlags(this, aUrlListener ? aUrlListener : this,
3609                                    nullptr, msgIds, flags, true);
3610     else
3611       imapService->SubtractMessageFlags(this,
3612                                         aUrlListener ? aUrlListener : this,
3613                                         nullptr, msgIds, flags, true);
3614   } else {
3615     rv = GetDatabase();
3616     if (NS_SUCCEEDED(rv) && mDatabase) {
3617       for (auto key : keys) {
3618         nsCOMPtr<nsIMsgOfflineImapOperation> op;
3619         rv = mDatabase->GetOfflineOpForKey(key, true, getter_AddRefs(op));
3620         SetFlag(nsMsgFolderFlags::OfflineEvents);
3621         if (NS_SUCCEEDED(rv) && op) {
3622           imapMessageFlagsType newFlags;
3623           op->GetNewFlags(&newFlags);
3624           op->SetFlagOperation(addFlags ? newFlags | flags : newFlags & ~flags);
3625         }
3626       }
3627       mDatabase->Commit(
3628           nsMsgDBCommitType::kLargeCommit);  // flush offline flags
3629     }
3630   }
3631   return rv;
3632 }
3633 
LiteSelect(nsIUrlListener * aUrlListener,nsIMsgWindow * aMsgWindow)3634 NS_IMETHODIMP nsImapMailFolder::LiteSelect(nsIUrlListener* aUrlListener,
3635                                            nsIMsgWindow* aMsgWindow) {
3636   nsresult rv;
3637   nsCOMPtr<nsIImapService> imapService =
3638       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
3639   NS_ENSURE_SUCCESS(rv, rv);
3640   return imapService->LiteSelectFolder(this, aUrlListener, aMsgWindow, nullptr);
3641 }
3642 
GetFolderOwnerUserName(nsACString & userName)3643 nsresult nsImapMailFolder::GetFolderOwnerUserName(nsACString& userName) {
3644   if ((mFlags & nsMsgFolderFlags::ImapPersonal) ||
3645       !(mFlags &
3646         (nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::ImapOtherUser))) {
3647     // this is one of our personal mail folders
3648     // return our username on this host
3649     nsCOMPtr<nsIMsgIncomingServer> server;
3650     nsresult rv = GetServer(getter_AddRefs(server));
3651     return NS_FAILED(rv) ? rv : server->GetUsername(userName);
3652   }
3653 
3654   // the only other type of owner is if it's in the other users' namespace
3655   if (!(mFlags & nsMsgFolderFlags::ImapOtherUser)) return NS_OK;
3656 
3657   if (m_ownerUserName.IsEmpty()) {
3658     nsCString onlineName;
3659     GetOnlineName(onlineName);
3660     m_ownerUserName = nsImapNamespaceList::GetFolderOwnerNameFromPath(
3661         GetNamespaceForFolder(), onlineName.get());
3662   }
3663   userName = m_ownerUserName;
3664   return NS_OK;
3665 }
3666 
GetNamespaceForFolder()3667 nsImapNamespace* nsImapMailFolder::GetNamespaceForFolder() {
3668   if (!m_namespace) {
3669 #ifdef DEBUG_bienvenu
3670     // Make sure this isn't causing us to open the database
3671     NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown,
3672                  "haven't set hierarchy delimiter");
3673 #endif
3674     nsCString serverKey;
3675     nsCString onlineName;
3676     GetServerKey(serverKey);
3677     GetOnlineName(onlineName);
3678     char hierarchyDelimiter;
3679     GetHierarchyDelimiter(&hierarchyDelimiter);
3680 
3681     m_namespace = nsImapNamespaceList::GetNamespaceForFolder(
3682         serverKey.get(), onlineName.get(), hierarchyDelimiter);
3683     NS_ASSERTION(m_namespace, "didn't get namespace for folder");
3684     if (m_namespace) {
3685       nsImapNamespaceList::SuggestHierarchySeparatorForNamespace(
3686           m_namespace, hierarchyDelimiter);
3687       m_folderIsNamespace = nsImapNamespaceList::GetFolderIsNamespace(
3688           serverKey.get(), onlineName.get(), hierarchyDelimiter, m_namespace);
3689     }
3690   }
3691   return m_namespace;
3692 }
3693 
SetNamespaceForFolder(nsImapNamespace * ns)3694 void nsImapMailFolder::SetNamespaceForFolder(nsImapNamespace* ns) {
3695 #ifdef DEBUG_bienvenu
3696   NS_ASSERTION(ns, "null namespace");
3697 #endif
3698   m_namespace = ns;
3699 }
3700 
FolderPrivileges(nsIMsgWindow * window)3701 NS_IMETHODIMP nsImapMailFolder::FolderPrivileges(nsIMsgWindow* window) {
3702   NS_ENSURE_ARG_POINTER(window);
3703   nsresult rv = NS_OK;  // if no window...
3704   if (!m_adminUrl.IsEmpty()) {
3705     nsCOMPtr<nsIExternalProtocolService> extProtService =
3706         do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
3707     if (extProtService) {
3708       nsAutoCString scheme;
3709       nsCOMPtr<nsIURI> uri;
3710       if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), m_adminUrl.get())))
3711         return rv;
3712       uri->GetScheme(scheme);
3713       if (!scheme.IsEmpty()) {
3714         // if the URL scheme does not correspond to an exposed protocol, then we
3715         // need to hand this link click over to the external protocol handler.
3716         bool isExposed;
3717         rv = extProtService->IsExposedProtocol(scheme.get(), &isExposed);
3718         if (NS_SUCCEEDED(rv) && !isExposed)
3719           return extProtService->LoadURI(uri, nullptr, nullptr, nullptr, false);
3720       }
3721     }
3722   } else {
3723     nsCOMPtr<nsIImapService> imapService =
3724         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
3725     NS_ENSURE_SUCCESS(rv, rv);
3726     rv = imapService->GetFolderAdminUrl(this, window, this, nullptr);
3727     if (NS_SUCCEEDED(rv)) m_urlRunning = true;
3728   }
3729   return rv;
3730 }
3731 
GetHasAdminUrl(bool * aBool)3732 NS_IMETHODIMP nsImapMailFolder::GetHasAdminUrl(bool* aBool) {
3733   NS_ENSURE_ARG_POINTER(aBool);
3734   nsCOMPtr<nsIImapIncomingServer> imapServer;
3735   nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
3736   nsCString manageMailAccountUrl;
3737   if (NS_SUCCEEDED(rv) && imapServer)
3738     rv = imapServer->GetManageMailAccountUrl(manageMailAccountUrl);
3739   *aBool = (NS_SUCCEEDED(rv) && !manageMailAccountUrl.IsEmpty());
3740   return rv;
3741 }
3742 
GetAdminUrl(nsACString & aResult)3743 NS_IMETHODIMP nsImapMailFolder::GetAdminUrl(nsACString& aResult) {
3744   aResult = m_adminUrl;
3745   return NS_OK;
3746 }
3747 
SetAdminUrl(const nsACString & adminUrl)3748 NS_IMETHODIMP nsImapMailFolder::SetAdminUrl(const nsACString& adminUrl) {
3749   m_adminUrl = adminUrl;
3750   return NS_OK;
3751 }
3752 
GetHdrParser(nsIMsgParseMailMsgState ** aHdrParser)3753 NS_IMETHODIMP nsImapMailFolder::GetHdrParser(
3754     nsIMsgParseMailMsgState** aHdrParser) {
3755   NS_ENSURE_ARG_POINTER(aHdrParser);
3756   NS_IF_ADDREF(*aHdrParser = m_msgParser);
3757   return NS_OK;
3758 }
3759 
3760 // this is used to issue an arbitrary imap command on the passed in msgs.
3761 // It assumes the command needs to be run in the selected state.
IssueCommandOnMsgs(const nsACString & command,const char * uids,nsIMsgWindow * aWindow,nsIURI ** url)3762 NS_IMETHODIMP nsImapMailFolder::IssueCommandOnMsgs(const nsACString& command,
3763                                                    const char* uids,
3764                                                    nsIMsgWindow* aWindow,
3765                                                    nsIURI** url) {
3766   nsresult rv;
3767   nsCOMPtr<nsIImapService> imapService =
3768       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
3769   NS_ENSURE_SUCCESS(rv, rv);
3770   return imapService->IssueCommandOnMsgs(this, aWindow, command,
3771                                          nsDependentCString(uids), url);
3772 }
3773 
FetchCustomMsgAttribute(const nsACString & attribute,const char * uids,nsIMsgWindow * aWindow,nsIURI ** url)3774 NS_IMETHODIMP nsImapMailFolder::FetchCustomMsgAttribute(
3775     const nsACString& attribute, const char* uids, nsIMsgWindow* aWindow,
3776     nsIURI** url) {
3777   nsresult rv;
3778   nsCOMPtr<nsIImapService> imapService =
3779       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
3780   NS_ENSURE_SUCCESS(rv, rv);
3781 
3782   return imapService->FetchCustomMsgAttribute(this, aWindow, attribute,
3783                                               nsDependentCString(uids), url);
3784 }
3785 
MoveIncorporatedMessage(nsIMsgDBHdr * mailHdr,nsIMsgDatabase * sourceDB,const nsACString & destFolderUri,nsIMsgFilter * filter,nsIMsgWindow * msgWindow)3786 nsresult nsImapMailFolder::MoveIncorporatedMessage(
3787     nsIMsgDBHdr* mailHdr, nsIMsgDatabase* sourceDB,
3788     const nsACString& destFolderUri, nsIMsgFilter* filter,
3789     nsIMsgWindow* msgWindow) {
3790   nsresult rv;
3791   if (m_moveCoalescer) {
3792     nsCOMPtr<nsIMsgFolder> destIFolder;
3793     rv = GetOrCreateFolder(destFolderUri, getter_AddRefs(destIFolder));
3794     NS_ENSURE_SUCCESS(rv, rv);
3795 
3796     if (destIFolder) {
3797       // check if the destination is a real folder (by checking for null parent)
3798       // and if it can file messages (e.g., servers or news folders can't file
3799       // messages). Or read only imap folders...
3800       bool canFileMessages = true;
3801       nsCOMPtr<nsIMsgFolder> parentFolder;
3802       destIFolder->GetParent(getter_AddRefs(parentFolder));
3803       if (parentFolder) destIFolder->GetCanFileMessages(&canFileMessages);
3804       if (filter && (!parentFolder || !canFileMessages)) {
3805         filter->SetEnabled(false);
3806         m_filterList->SaveToDefaultFile();
3807         destIFolder->ThrowAlertMsg("filterDisabled", msgWindow);
3808         return NS_MSG_NOT_A_MAIL_FOLDER;
3809       }
3810       // put the header into the source db, since it needs to be there when we
3811       // copy it and we need a valid header to pass to
3812       // StartAsyncCopyMessagesInto
3813       nsMsgKey keyToFilter;
3814       mailHdr->GetMessageKey(&keyToFilter);
3815 
3816       if (sourceDB && destIFolder) {
3817         bool imapDeleteIsMoveToTrash = DeleteIsMoveToTrash();
3818         m_moveCoalescer->AddMove(destIFolder, keyToFilter);
3819         // For each folder, we need to keep track of the ids we want to move to
3820         // that folder - we used to store them in the MSG_FolderInfo and then
3821         // when we'd finished downloading headers, we'd iterate through all the
3822         // folders looking for the ones that needed messages moved into them -
3823         // perhaps instead we could keep track of nsIMsgFolder,
3824         // nsTArray<nsMsgKey> pairs here in the imap code. nsTArray<nsMsgKey>
3825         // *idsToMoveFromInbox = msgFolder->GetImapIdsToMoveFromInbox();
3826         // idsToMoveFromInbox->AppendElement(keyToFilter);
3827         if (imapDeleteIsMoveToTrash) {
3828         }
3829         bool isRead = false;
3830         mailHdr->GetIsRead(&isRead);
3831         if (imapDeleteIsMoveToTrash) rv = NS_OK;
3832       }
3833     }
3834   } else
3835     rv = NS_ERROR_UNEXPECTED;
3836 
3837   // we have to return an error because we do not actually move the message
3838   // it is done async and that can fail
3839   return rv;
3840 }
3841 
3842 /**
3843  * This method assumes that key arrays and flag states are sorted by increasing
3844  * key.
3845  */
FindKeysToDelete(const nsTArray<nsMsgKey> & existingKeys,nsTArray<nsMsgKey> & keysToDelete,nsIImapFlagAndUidState * flagState,uint32_t boxFlags)3846 void nsImapMailFolder::FindKeysToDelete(const nsTArray<nsMsgKey>& existingKeys,
3847                                         nsTArray<nsMsgKey>& keysToDelete,
3848                                         nsIImapFlagAndUidState* flagState,
3849                                         uint32_t boxFlags) {
3850   bool showDeletedMessages = ShowDeletedMessages();
3851   int32_t numMessageInFlagState;
3852   bool partialUIDFetch;
3853   uint32_t uidOfMessage;
3854   imapMessageFlagsType flags;
3855 
3856   flagState->GetNumberOfMessages(&numMessageInFlagState);
3857   flagState->GetPartialUIDFetch(&partialUIDFetch);
3858 
3859   // if we're doing a partialUIDFetch, just delete the keys from the db
3860   // that have the deleted flag set (if not using imap delete model)
3861   // and return.
3862   if (partialUIDFetch) {
3863     if (!showDeletedMessages) {
3864       for (uint32_t i = 0; (int32_t)i < numMessageInFlagState; i++) {
3865         flagState->GetUidOfMessage(i, &uidOfMessage);
3866         // flag state will be zero filled up to first real uid, so ignore those.
3867         if (uidOfMessage) {
3868           flagState->GetMessageFlags(i, &flags);
3869           if (flags & kImapMsgDeletedFlag)
3870             keysToDelete.AppendElement(uidOfMessage);
3871         }
3872       }
3873     } else if (boxFlags & kJustExpunged) {
3874       // we've just issued an expunge with a partial flag state. We should
3875       // delete headers with the imap deleted flag set, because we can't
3876       // tell from the expunge response which messages were deleted.
3877       nsCOMPtr<nsIMsgEnumerator> hdrs;
3878       nsresult rv = GetMessages(getter_AddRefs(hdrs));
3879       NS_ENSURE_SUCCESS_VOID(rv);
3880       bool hasMore = false;
3881       while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
3882         nsCOMPtr<nsIMsgDBHdr> header;
3883         rv = hdrs->GetNext(getter_AddRefs(header));
3884         NS_ENSURE_SUCCESS_VOID(rv);
3885         uint32_t msgFlags;
3886         header->GetFlags(&msgFlags);
3887         if (msgFlags & nsMsgMessageFlags::IMAPDeleted) {
3888           nsMsgKey msgKey;
3889           header->GetMessageKey(&msgKey);
3890           keysToDelete.AppendElement(msgKey);
3891         }
3892       }
3893     }
3894     return;
3895   }
3896   // otherwise, we have a complete set of uid's and flags, so we delete
3897   // anything that's in existingKeys but not in the flag state, as well
3898   // as messages with the deleted flag set.
3899   uint32_t total = existingKeys.Length();
3900   int onlineIndex = 0;  // current index into flagState
3901   for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++) {
3902     while (
3903         (onlineIndex < numMessageInFlagState) &&
3904         NS_SUCCEEDED(flagState->GetUidOfMessage(onlineIndex, &uidOfMessage)) &&
3905         (existingKeys[keyIndex] > uidOfMessage))
3906       onlineIndex++;
3907 
3908     flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
3909     flagState->GetMessageFlags(onlineIndex, &flags);
3910     // delete this key if it is not there or marked deleted
3911     if ((onlineIndex >= numMessageInFlagState) ||
3912         (existingKeys[keyIndex] != uidOfMessage) ||
3913         ((flags & kImapMsgDeletedFlag) && !showDeletedMessages)) {
3914       nsMsgKey doomedKey = existingKeys[keyIndex];
3915       if ((int32_t)doomedKey <= 0 && doomedKey != nsMsgKey_None) continue;
3916 
3917       keysToDelete.AppendElement(existingKeys[keyIndex]);
3918     }
3919 
3920     flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
3921     if (existingKeys[keyIndex] == uidOfMessage) onlineIndex++;
3922   }
3923 }
3924 
FindKeysToAdd(const nsTArray<nsMsgKey> & existingKeys,nsTArray<nsMsgKey> & keysToFetch,uint32_t & numNewUnread,nsIImapFlagAndUidState * flagState)3925 void nsImapMailFolder::FindKeysToAdd(const nsTArray<nsMsgKey>& existingKeys,
3926                                      nsTArray<nsMsgKey>& keysToFetch,
3927                                      uint32_t& numNewUnread,
3928                                      nsIImapFlagAndUidState* flagState) {
3929   bool showDeletedMessages = ShowDeletedMessages();
3930   int dbIndex = 0;  // current index into existingKeys
3931   int32_t existTotal, numberOfKnownKeys;
3932   int32_t messageIndex;
3933 
3934   numNewUnread = 0;
3935   existTotal = numberOfKnownKeys = existingKeys.Length();
3936   flagState->GetNumberOfMessages(&messageIndex);
3937   bool partialUIDFetch;
3938   flagState->GetPartialUIDFetch(&partialUIDFetch);
3939 
3940   for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++) {
3941     uint32_t uidOfMessage;
3942     flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
3943     while ((flagIndex < numberOfKnownKeys) && (dbIndex < existTotal) &&
3944            existingKeys[dbIndex] < uidOfMessage)
3945       dbIndex++;
3946 
3947     if ((flagIndex >= numberOfKnownKeys) || (dbIndex >= existTotal) ||
3948         (existingKeys[dbIndex] != uidOfMessage)) {
3949       numberOfKnownKeys++;
3950 
3951       imapMessageFlagsType flags;
3952       flagState->GetMessageFlags(flagIndex, &flags);
3953       NS_ASSERTION(uidOfMessage != nsMsgKey_None, "got invalid msg key");
3954       if (uidOfMessage && uidOfMessage != nsMsgKey_None &&
3955           (showDeletedMessages || !(flags & kImapMsgDeletedFlag))) {
3956         if (mDatabase) {
3957           bool dbContainsKey;
3958           if (NS_SUCCEEDED(
3959                   mDatabase->ContainsKey(uidOfMessage, &dbContainsKey)) &&
3960               dbContainsKey) {
3961             // this is expected in the partial uid fetch case because the
3962             // flag state does not contain all messages, so the db has
3963             // messages the flag state doesn't know about.
3964             if (!partialUIDFetch) NS_ERROR("db has key - flagState messed up?");
3965             continue;
3966           }
3967         }
3968         keysToFetch.AppendElement(uidOfMessage);
3969         if (!(flags & kImapMsgSeenFlag)) numNewUnread++;
3970       }
3971     }
3972   }
3973 }
3974 
GetMsgHdrsToDownload(bool * aMoreToDownload,int32_t * aTotalCount,nsTArray<nsMsgKey> & aKeys)3975 NS_IMETHODIMP nsImapMailFolder::GetMsgHdrsToDownload(
3976     bool* aMoreToDownload, int32_t* aTotalCount, nsTArray<nsMsgKey>& aKeys) {
3977   NS_ENSURE_ARG_POINTER(aMoreToDownload);
3978   NS_ENSURE_ARG_POINTER(aTotalCount);
3979   aKeys.Clear();
3980 
3981   *aMoreToDownload = false;
3982   *aTotalCount = m_totalKeysToFetch;
3983   if (m_keysToFetch.IsEmpty()) {
3984     return NS_OK;
3985   }
3986 
3987   // if folder isn't open in a window, no reason to limit the number of headers
3988   // we download.
3989   nsCOMPtr<nsIMsgMailSession> session =
3990       do_GetService(NS_MSGMAILSESSION_CONTRACTID);
3991   bool folderOpen = false;
3992   if (session) session->IsFolderOpenInWindow(this, &folderOpen);
3993 
3994   int32_t hdrChunkSize = 200;
3995   if (folderOpen) {
3996     nsresult rv;
3997     nsCOMPtr<nsIPrefBranch> prefBranch(
3998         do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
3999     NS_ENSURE_SUCCESS(rv, rv);
4000     if (prefBranch)
4001       prefBranch->GetIntPref("mail.imap.hdr_chunk_size", &hdrChunkSize);
4002   }
4003   int32_t numKeysToFetch = m_keysToFetch.Length();
4004   int32_t startIndex = 0;
4005   if (folderOpen && hdrChunkSize > 0 &&
4006       (int32_t)m_keysToFetch.Length() > hdrChunkSize) {
4007     numKeysToFetch = hdrChunkSize;
4008     *aMoreToDownload = true;
4009     startIndex = m_keysToFetch.Length() - hdrChunkSize;
4010   }
4011   aKeys.AppendElements(&m_keysToFetch[startIndex], numKeysToFetch);
4012   // Remove these for the incremental header download case, so that
4013   // we know we don't have to download them again.
4014   m_keysToFetch.RemoveElementsAt(startIndex, numKeysToFetch);
4015 
4016   return NS_OK;
4017 }
4018 
PrepareToAddHeadersToMailDB(nsIImapProtocol * aProtocol)4019 void nsImapMailFolder::PrepareToAddHeadersToMailDB(nsIImapProtocol* aProtocol) {
4020   // now, tell it we don't need any bodies.
4021   nsTArray<nsMsgKey> noBodies;
4022   aProtocol->NotifyBodysToDownload(noBodies);
4023 }
4024 
TweakHeaderFlags(nsIImapProtocol * aProtocol,nsIMsgDBHdr * tweakMe)4025 void nsImapMailFolder::TweakHeaderFlags(nsIImapProtocol* aProtocol,
4026                                         nsIMsgDBHdr* tweakMe) {
4027   if (mDatabase && aProtocol && tweakMe) {
4028     tweakMe->SetMessageKey(m_curMsgUid);
4029     tweakMe->SetMessageSize(m_nextMessageByteLength);
4030 
4031     bool foundIt = false;
4032     imapMessageFlagsType imap_flags;
4033 
4034     nsCString customFlags;
4035     nsresult rv = aProtocol->GetFlagsForUID(m_curMsgUid, &foundIt, &imap_flags,
4036                                             getter_Copies(customFlags));
4037     if (NS_SUCCEEDED(rv) && foundIt) {
4038       // make a mask and clear these message flags
4039       uint32_t mask = nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |
4040                       nsMsgMessageFlags::Marked |
4041                       nsMsgMessageFlags::IMAPDeleted |
4042                       nsMsgMessageFlags::Labels;
4043       uint32_t dbHdrFlags;
4044 
4045       tweakMe->GetFlags(&dbHdrFlags);
4046       tweakMe->AndFlags(~mask, &dbHdrFlags);
4047 
4048       // set the new value for these flags
4049       uint32_t newFlags = 0;
4050       if (imap_flags & kImapMsgSeenFlag)
4051         newFlags |= nsMsgMessageFlags::Read;
4052       else  // if (imap_flags & kImapMsgRecentFlag)
4053         newFlags |= nsMsgMessageFlags::New;
4054 
4055       // Okay here is the MDN needed logic (if DNT header seen):
4056       /* if server support user defined flag:
4057          XXX TODO: Fix badly formatted comment which doesn't reflect the code.
4058                     MDNSent flag set => clear kMDNNeeded flag
4059                     MDNSent flag not set => do nothing, leave kMDNNeeded on
4060                     else if
4061                     not nsMsgMessageFlags::New => clear kMDNNeeded flag
4062                    nsMsgMessageFlags::New => do nothing, leave kMDNNeeded on
4063                */
4064       uint16_t userFlags;
4065       rv = aProtocol->GetSupportedUserFlags(&userFlags);
4066       if (NS_SUCCEEDED(rv) && (userFlags & (kImapMsgSupportUserFlag |
4067                                             kImapMsgSupportMDNSentFlag))) {
4068         if (imap_flags & kImapMsgMDNSentFlag) {
4069           newFlags |= nsMsgMessageFlags::MDNReportSent;
4070           if (dbHdrFlags & nsMsgMessageFlags::MDNReportNeeded)
4071             tweakMe->AndFlags(~nsMsgMessageFlags::MDNReportNeeded, &dbHdrFlags);
4072         }
4073       }
4074 
4075       if (imap_flags & kImapMsgAnsweredFlag)
4076         newFlags |= nsMsgMessageFlags::Replied;
4077       if (imap_flags & kImapMsgFlaggedFlag)
4078         newFlags |= nsMsgMessageFlags::Marked;
4079       if (imap_flags & kImapMsgDeletedFlag)
4080         newFlags |= nsMsgMessageFlags::IMAPDeleted;
4081       if (imap_flags & kImapMsgForwardedFlag)
4082         newFlags |= nsMsgMessageFlags::Forwarded;
4083 
4084       // db label flags are 0x0E000000 and imap label flags are 0x0E00
4085       // so we need to shift 16 bits to the left to convert them.
4086       if (imap_flags & kImapMsgLabelFlags) {
4087         // we need to set label attribute on header because the dbview code
4088         // does msgHdr->GetLabel when asked to paint a row
4089         tweakMe->SetLabel((imap_flags & kImapMsgLabelFlags) >> 9);
4090         newFlags |= (imap_flags & kImapMsgLabelFlags) << 16;
4091       }
4092       if (newFlags) tweakMe->OrFlags(newFlags, &dbHdrFlags);
4093       if (!customFlags.IsEmpty())
4094         (void)HandleCustomFlags(m_curMsgUid, tweakMe, userFlags, customFlags);
4095     }
4096   }
4097 }
4098 
4099 NS_IMETHODIMP
SetupMsgWriteStream(nsIFile * aFile,bool addDummyEnvelope)4100 nsImapMailFolder::SetupMsgWriteStream(nsIFile* aFile, bool addDummyEnvelope) {
4101   nsresult rv;
4102   aFile->Remove(false);
4103   rv = MsgNewBufferedFileOutputStream(
4104       getter_AddRefs(m_tempMessageStream), aFile,
4105       PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 00700);
4106   if (m_tempMessageStream && addDummyEnvelope) {
4107     nsAutoCString result;
4108     char* ct;
4109     uint32_t writeCount;
4110     time_t now = time((time_t*)0);
4111     ct = ctime(&now);
4112     ct[24] = 0;
4113     result = "From - ";
4114     result += ct;
4115     result += MSG_LINEBREAK;
4116 
4117     m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
4118     result = "X-Mozilla-Status: 0001";
4119     result += MSG_LINEBREAK;
4120     m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
4121     result = "X-Mozilla-Status2: 00000000";
4122     result += MSG_LINEBREAK;
4123     m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
4124   }
4125   return rv;
4126 }
4127 
DownloadMessagesForOffline(nsTArray<RefPtr<nsIMsgDBHdr>> const & messages,nsIMsgWindow * window)4128 NS_IMETHODIMP nsImapMailFolder::DownloadMessagesForOffline(
4129     nsTArray<RefPtr<nsIMsgDBHdr>> const& messages, nsIMsgWindow* window) {
4130   nsAutoCString messageIds;
4131   nsTArray<nsMsgKey> srcKeyArray;
4132   nsresult rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
4133   if (NS_FAILED(rv) || messageIds.IsEmpty()) return rv;
4134 
4135   nsCOMPtr<nsIImapService> imapService =
4136       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
4137   NS_ENSURE_SUCCESS(rv, rv);
4138 
4139   rv = AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
4140   if (NS_FAILED(rv)) {
4141     ThrowAlertMsg("operationFailedFolderBusy", window);
4142     return rv;
4143   }
4144   return imapService->DownloadMessagesForOffline(messageIds, this, this,
4145                                                  window);
4146 }
4147 
DownloadAllForOffline(nsIUrlListener * listener,nsIMsgWindow * msgWindow)4148 NS_IMETHODIMP nsImapMailFolder::DownloadAllForOffline(nsIUrlListener* listener,
4149                                                       nsIMsgWindow* msgWindow) {
4150   nsresult rv;
4151   nsCOMPtr<nsIURI> runningURI;
4152   bool noSelect;
4153   GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
4154 
4155   if (!noSelect) {
4156     nsAutoCString messageIdsToDownload;
4157     nsTArray<nsMsgKey> msgsToDownload;
4158 
4159     GetDatabase();
4160     m_downloadingFolderForOfflineUse = true;
4161 
4162     rv = AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
4163     if (NS_FAILED(rv)) {
4164       m_downloadingFolderForOfflineUse = false;
4165       ThrowAlertMsg("operationFailedFolderBusy", msgWindow);
4166       return rv;
4167     }
4168     nsCOMPtr<nsIImapService> imapService =
4169         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
4170     NS_ENSURE_SUCCESS(rv, rv);
4171 
4172     // Selecting the folder with nsIImapUrl::shouldStoreMsgOffline true will
4173     // cause us to fetch any message bodies we don't have.
4174     m_urlListener = listener;
4175     rv = imapService->SelectFolder(this, this, msgWindow,
4176                                    getter_AddRefs(runningURI));
4177     if (NS_SUCCEEDED(rv)) {
4178       nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(runningURI));
4179       if (imapUrl) imapUrl->SetStoreResultsOffline(true);
4180       m_urlRunning = true;
4181     }
4182   } else
4183     rv = NS_MSG_FOLDER_UNREADABLE;
4184   return rv;
4185 }
4186 
4187 NS_IMETHODIMP
ParseAdoptedMsgLine(const char * adoptedMessageLine,nsMsgKey uidOfMessage,nsIImapUrl * aImapUrl)4188 nsImapMailFolder::ParseAdoptedMsgLine(const char* adoptedMessageLine,
4189                                       nsMsgKey uidOfMessage,
4190                                       nsIImapUrl* aImapUrl) {
4191   NS_ENSURE_ARG_POINTER(aImapUrl);
4192   uint32_t count = 0;
4193   nsresult rv;
4194   // remember the uid of the message we're downloading.
4195   m_curMsgUid = uidOfMessage;
4196   if (!m_offlineHeader) {
4197     rv = GetMessageHeader(uidOfMessage, getter_AddRefs(m_offlineHeader));
4198     if (NS_SUCCEEDED(rv) && !m_offlineHeader) rv = NS_ERROR_UNEXPECTED;
4199     NS_ENSURE_SUCCESS(rv, rv);
4200     rv = StartNewOfflineMessage();
4201     NS_ENSURE_SUCCESS(rv, rv);
4202   }
4203   // adoptedMessageLine is actually a string with a lot of message lines,
4204   // separated by native line terminators we need to count the number of
4205   // MSG_LINEBREAK's to determine how much to increment m_numOfflineMsgLines by.
4206   const char* nextLine = adoptedMessageLine;
4207   do {
4208     m_numOfflineMsgLines++;
4209     nextLine = PL_strstr(nextLine, MSG_LINEBREAK);
4210     if (nextLine) nextLine += MSG_LINEBREAK_LEN;
4211   } while (nextLine && *nextLine);
4212 
4213   if (m_tempMessageStream) {
4214     nsCOMPtr<nsISeekableStream> seekable(
4215         do_QueryInterface(m_tempMessageStream));
4216     if (seekable) seekable->Seek(PR_SEEK_END, 0);
4217     rv = m_tempMessageStream->Write(adoptedMessageLine,
4218                                     PL_strlen(adoptedMessageLine), &count);
4219     NS_ENSURE_SUCCESS(rv, rv);
4220   }
4221   return NS_OK;
4222 }
4223 
EndOfflineDownload()4224 void nsImapMailFolder::EndOfflineDownload() {
4225   if (m_tempMessageStream) {
4226     m_tempMessageStream->Close();
4227     m_tempMessageStream = nullptr;
4228     ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
4229     if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
4230   }
4231   m_offlineHeader = nullptr;
4232 }
4233 
4234 NS_IMETHODIMP
NormalEndMsgWriteStream(nsMsgKey uidOfMessage,bool markRead,nsIImapUrl * imapUrl,int32_t updatedMessageSize)4235 nsImapMailFolder::NormalEndMsgWriteStream(nsMsgKey uidOfMessage, bool markRead,
4236                                           nsIImapUrl* imapUrl,
4237                                           int32_t updatedMessageSize) {
4238   if (updatedMessageSize != -1) {
4239     // retrieve the message header to update size, if we don't already have it
4240     nsCOMPtr<nsIMsgDBHdr> msgHeader = m_offlineHeader;
4241     if (!msgHeader) GetMessageHeader(uidOfMessage, getter_AddRefs(msgHeader));
4242     if (msgHeader) {
4243       uint32_t msgSize;
4244       msgHeader->GetMessageSize(&msgSize);
4245       MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
4246               ("Updating stored message size from %u, new size %d", msgSize,
4247                updatedMessageSize));
4248       msgHeader->SetMessageSize(updatedMessageSize);
4249       // only commit here if this isn't an offline message
4250       // offline header gets committed in EndNewOfflineMessage() called below
4251       if (mDatabase && !m_offlineHeader)
4252         mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
4253     } else
4254       NS_WARNING(
4255           "Failed to get message header when trying to update message size");
4256   }
4257 
4258   if (m_offlineHeader) EndNewOfflineMessage();
4259 
4260   m_curMsgUid = uidOfMessage;
4261 
4262   // Apply filter now if it needed a body
4263   if (m_filterListRequiresBody) {
4264     if (m_filterList) {
4265       nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
4266       GetMessageHeader(uidOfMessage, getter_AddRefs(newMsgHdr));
4267       GetMoveCoalescer();
4268       nsCOMPtr<nsIMsgWindow> msgWindow;
4269       if (imapUrl) {
4270         nsresult rv;
4271         nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
4272         msgUrl = do_QueryInterface(imapUrl, &rv);
4273         if (msgUrl && NS_SUCCEEDED(rv))
4274           msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
4275       }
4276       m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr,
4277                                       this, mDatabase, EmptyCString(), this,
4278                                       msgWindow);
4279       NotifyFolderEvent(kFiltersApplied);
4280     }
4281     // Process filter plugins and other items normally done at the end of
4282     // HeaderFetchCompleted.
4283     bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
4284     PlaybackCoalescedOperations();
4285 
4286     bool filtersRun;
4287     CallFilterPlugins(nullptr, &filtersRun);
4288     int32_t numNewBiffMsgs = 0;
4289     if (m_performingBiff) GetNumNewMessages(false, &numNewBiffMsgs);
4290 
4291     if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
4292         (!pendingMoves || !ShowPreviewText())) {
4293       // If we are performing biff for this folder, tell the
4294       // stand-alone biff about the new high water mark
4295       // We must ensure that the server knows that we are performing biff.
4296       // Otherwise the stand-alone biff won't fire.
4297       nsCOMPtr<nsIMsgIncomingServer> server;
4298       if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
4299         server->SetPerformingBiff(true);
4300 
4301       SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
4302       if (server) server->SetPerformingBiff(false);
4303       m_performingBiff = false;
4304     }
4305 
4306     if (m_filterList) (void)m_filterList->FlushLogIfNecessary();
4307   }
4308 
4309   return NS_OK;
4310 }
4311 
4312 NS_IMETHODIMP
AbortMsgWriteStream()4313 nsImapMailFolder::AbortMsgWriteStream() {
4314   m_offlineHeader = nullptr;
4315   return NS_ERROR_FAILURE;
4316 }
4317 
4318 // message move/copy related methods
4319 NS_IMETHODIMP
OnlineCopyCompleted(nsIImapProtocol * aProtocol,ImapOnlineCopyState aCopyState)4320 nsImapMailFolder::OnlineCopyCompleted(nsIImapProtocol* aProtocol,
4321                                       ImapOnlineCopyState aCopyState) {
4322   NS_ENSURE_ARG_POINTER(aProtocol);
4323 
4324   nsresult rv;
4325   if (aCopyState == ImapOnlineCopyStateType::kSuccessfulCopy) {
4326     nsCOMPtr<nsIImapUrl> imapUrl;
4327     rv = aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
4328     if (NS_FAILED(rv) || !imapUrl) return NS_ERROR_FAILURE;
4329     nsImapAction action;
4330     rv = imapUrl->GetImapAction(&action);
4331     if (NS_FAILED(rv)) return rv;
4332     if (action != nsIImapUrl::nsImapOnlineToOfflineMove)
4333       return NS_ERROR_FAILURE;  // don't assert here...
4334     nsCString messageIds;
4335     rv = imapUrl->GetListOfMessageIds(messageIds);
4336     if (NS_FAILED(rv)) return rv;
4337     nsCOMPtr<nsIImapService> imapService =
4338         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
4339     NS_ENSURE_SUCCESS(rv, rv);
4340     return imapService->AddMessageFlags(this, nullptr, nullptr, messageIds,
4341                                         kImapMsgDeletedFlag, true);
4342   }
4343   /* unhandled copystate */
4344   if (m_copyState)  // whoops, this is the wrong folder - should use the source
4345                     // folder
4346   {
4347     nsCOMPtr<nsIMsgFolder> srcFolder;
4348     srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv);
4349     if (srcFolder) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
4350   } else
4351     rv = NS_ERROR_FAILURE;
4352 
4353   return rv;
4354 }
4355 
4356 NS_IMETHODIMP
CloseMockChannel(nsIImapMockChannel * aChannel)4357 nsImapMailFolder::CloseMockChannel(nsIImapMockChannel* aChannel) {
4358   aChannel->Close();
4359   return NS_OK;
4360 }
4361 
4362 NS_IMETHODIMP
ReleaseUrlCacheEntry(nsIMsgMailNewsUrl * aUrl)4363 nsImapMailFolder::ReleaseUrlCacheEntry(nsIMsgMailNewsUrl* aUrl) {
4364   NS_ENSURE_ARG_POINTER(aUrl);
4365   return aUrl->SetMemCacheEntry(nullptr);
4366 }
4367 
4368 NS_IMETHODIMP
BeginMessageUpload()4369 nsImapMailFolder::BeginMessageUpload() { return NS_ERROR_FAILURE; }
4370 
HandleCustomFlags(nsMsgKey uidOfMessage,nsIMsgDBHdr * dbHdr,uint16_t userFlags,nsCString & keywords)4371 nsresult nsImapMailFolder::HandleCustomFlags(nsMsgKey uidOfMessage,
4372                                              nsIMsgDBHdr* dbHdr,
4373                                              uint16_t userFlags,
4374                                              nsCString& keywords) {
4375   nsresult rv = GetDatabase();
4376   NS_ENSURE_SUCCESS(rv, rv);
4377 
4378   ToLowerCase(keywords);
4379   bool messageClassified = true;
4380   // Mac Mail uses "NotJunk"
4381   if (keywords.Find("NonJunk", /* ignoreCase = */ true) != kNotFound ||
4382       keywords.Find("NotJunk", /* ignoreCase = */ true) != kNotFound) {
4383     nsAutoCString msgJunkScore;
4384     msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
4385     mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
4386   }
4387   // ### TODO: we really should parse the keywords into space delimited keywords
4388   // before checking
4389   else if (keywords.Find("Junk", /* ignoreCase = */ true) != kNotFound) {
4390     uint32_t newFlags;
4391     dbHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
4392     nsAutoCString msgJunkScore;
4393     msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_SPAM_SCORE);
4394     mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
4395   } else
4396     messageClassified = false;
4397   if (messageClassified) {
4398     // only set the junkscore origin if it wasn't set before.
4399     nsCString existingProperty;
4400     dbHdr->GetStringProperty("junkscoreorigin",
4401                              getter_Copies(existingProperty));
4402     if (existingProperty.IsEmpty())
4403       dbHdr->SetStringProperty("junkscoreorigin", "imapflag");
4404   }
4405 
4406   if (!(userFlags & kImapMsgSupportUserFlag)) {
4407     nsCString localKeywords;
4408     nsCString prevKeywords;
4409     dbHdr->GetStringProperty("keywords", getter_Copies(localKeywords));
4410     dbHdr->GetStringProperty("prevkeywords", getter_Copies(prevKeywords));
4411     // localKeywords: tags currently stored in database for the message.
4412     // keywords: tags stored in server and obtained when flags for the message
4413     //           were last fetched. (Parameter of this function.)
4414     // prevKeywords: saved keywords from previous call of this function.
4415     // clang-format off
4416     MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug,
4417             ("UID=%" PRIu32 ", localKeywords=|%s| keywords=|%s|, prevKeywords=|%s|",
4418              uidOfMessage, localKeywords.get(), keywords.get(), prevKeywords.get()));
4419     // clang-format on
4420 
4421     // Store keywords to detect changes on next call of this function.
4422     dbHdr->SetStringProperty("prevkeywords", keywords.get());
4423 
4424     // Parse the space separated strings into arrays.
4425     nsTArray<nsCString> localKeywordArray;
4426     nsTArray<nsCString> keywordArray;
4427     nsTArray<nsCString> prevKeywordArray;
4428     ParseString(localKeywords, ' ', localKeywordArray);
4429     ParseString(keywords, ' ', keywordArray);
4430     ParseString(prevKeywords, ' ', prevKeywordArray);
4431 
4432     // If keyword not received now but was the last time, remove it from
4433     // the localKeywords. This means the keyword was removed by another user
4434     // sharing the folder.
4435     for (uint32_t i = 0; i < prevKeywordArray.Length(); i++) {
4436       bool inRcvd = keywordArray.Contains(prevKeywordArray[i]);
4437       bool inLocal = localKeywordArray.Contains(prevKeywordArray[i]);
4438       if (!inRcvd && inLocal)
4439         localKeywordArray.RemoveElement(prevKeywordArray[i]);
4440     }
4441 
4442     // Combine local and rcvd keyword arrays into a single string
4443     // so it can be passed to SetStringProperty(). If element of
4444     // local already in rcvd, avoid duplicates in combined string.
4445     nsAutoCString combinedKeywords;
4446     for (uint32_t i = 0; i < localKeywordArray.Length(); i++) {
4447       if (!keywordArray.Contains(localKeywordArray[i])) {
4448         combinedKeywords.Append(localKeywordArray[i]);
4449         combinedKeywords.Append(' ');
4450       }
4451     }
4452     for (uint32_t i = 0; i < keywordArray.Length(); i++) {
4453       combinedKeywords.Append(keywordArray[i]);
4454       combinedKeywords.Append(' ');
4455     }
4456     MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug,
4457             ("combinedKeywords stored = |%s|", combinedKeywords.get()));
4458     // combinedKeywords are tags being stored in database for the message.
4459     return dbHdr->SetStringProperty("keywords", combinedKeywords.get());
4460   }
4461   return (userFlags & kImapMsgSupportUserFlag)
4462              ? dbHdr->SetStringProperty("keywords", keywords.get())
4463              : NS_OK;
4464 }
4465 
4466 // synchronize the message flags in the database with the server flags
SyncFlags(nsIImapFlagAndUidState * flagState)4467 nsresult nsImapMailFolder::SyncFlags(nsIImapFlagAndUidState* flagState) {
4468   nsresult rv = GetDatabase();  // we need a database for this
4469   NS_ENSURE_SUCCESS(rv, rv);
4470   bool partialUIDFetch;
4471   flagState->GetPartialUIDFetch(&partialUIDFetch);
4472 
4473   // update all of the database flags
4474   int32_t messageIndex;
4475   uint32_t messageSize;
4476 
4477   // Take this opportunity to recalculate the folder size, if we're not a
4478   // partial (condstore) fetch.
4479   int64_t newFolderSize = 0;
4480 
4481   flagState->GetNumberOfMessages(&messageIndex);
4482 
4483   uint16_t supportedUserFlags;
4484   flagState->GetSupportedUserFlags(&supportedUserFlags);
4485 
4486   for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++) {
4487     uint32_t uidOfMessage;
4488     flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
4489     imapMessageFlagsType flags;
4490     flagState->GetMessageFlags(flagIndex, &flags);
4491     nsCOMPtr<nsIMsgDBHdr> dbHdr;
4492     bool containsKey;
4493     rv = mDatabase->ContainsKey(uidOfMessage, &containsKey);
4494     // if we don't have the header, don't diddle the flags.
4495     // GetMsgHdrForKey will create the header if it doesn't exist.
4496     if (NS_FAILED(rv) || !containsKey) continue;
4497 
4498     rv = mDatabase->GetMsgHdrForKey(uidOfMessage, getter_AddRefs(dbHdr));
4499     if (NS_SUCCEEDED(dbHdr->GetMessageSize(&messageSize)))
4500       newFolderSize += messageSize;
4501 
4502     nsCString keywords;
4503     if (NS_SUCCEEDED(
4504             flagState->GetCustomFlags(uidOfMessage, getter_Copies(keywords))))
4505       HandleCustomFlags(uidOfMessage, dbHdr, supportedUserFlags, keywords);
4506 
4507     NotifyMessageFlagsFromHdr(dbHdr, uidOfMessage, flags);
4508   }
4509   if (!partialUIDFetch && newFolderSize != mFolderSize) {
4510     int64_t oldFolderSize = mFolderSize;
4511     mFolderSize = newFolderSize;
4512     NotifyIntPropertyChanged(kFolderSize, oldFolderSize, mFolderSize);
4513   }
4514 
4515   return NS_OK;
4516 }
4517 
4518 // helper routine to sync the flags on a given header
NotifyMessageFlagsFromHdr(nsIMsgDBHdr * dbHdr,nsMsgKey msgKey,uint32_t flags)4519 nsresult nsImapMailFolder::NotifyMessageFlagsFromHdr(nsIMsgDBHdr* dbHdr,
4520                                                      nsMsgKey msgKey,
4521                                                      uint32_t flags) {
4522   nsresult rv = GetDatabase();
4523   NS_ENSURE_SUCCESS(rv, rv);
4524 
4525   // Although it may seem strange to keep a local reference of mDatabase here,
4526   // the current lifetime management of databases requires that methods
4527   // sometimes null the database when they think they opened it. Unfortunately
4528   // experience shows this happens when we don't expect, so for crash protection
4529   // best practice with the current flawed database management is to keep a
4530   // local reference when there will be complex calls in a method. See bug
4531   // 1312254.
4532   nsCOMPtr<nsIMsgDatabase> database(mDatabase);
4533   NS_ENSURE_STATE(database);
4534 
4535   database->MarkHdrRead(dbHdr, (flags & kImapMsgSeenFlag) != 0, nullptr);
4536   database->MarkHdrReplied(dbHdr, (flags & kImapMsgAnsweredFlag) != 0, nullptr);
4537   database->MarkHdrMarked(dbHdr, (flags & kImapMsgFlaggedFlag) != 0, nullptr);
4538   database->MarkImapDeleted(msgKey, (flags & kImapMsgDeletedFlag) != 0,
4539                             nullptr);
4540 
4541   uint32_t supportedFlags;
4542   GetSupportedUserFlags(&supportedFlags);
4543   if (supportedFlags & kImapMsgSupportForwardedFlag)
4544     database->MarkForwarded(msgKey, (flags & kImapMsgForwardedFlag) != 0,
4545                             nullptr);
4546   // this turns on labels, but it doesn't handle the case where the user
4547   // unlabels a message on one machine, and expects it to be unlabeled
4548   // on their other machines. If I turn that on, I'll be removing all the labels
4549   // that were assigned before we started storing them on the server, which will
4550   // make some people very unhappy.
4551   if (flags & kImapMsgLabelFlags)
4552     database->SetLabel(msgKey, (flags & kImapMsgLabelFlags) >> 9);
4553   else {
4554     if (supportedFlags & kImapMsgLabelFlags) database->SetLabel(msgKey, 0);
4555   }
4556   if (supportedFlags & kImapMsgSupportMDNSentFlag)
4557     database->MarkMDNSent(msgKey, (flags & kImapMsgMDNSentFlag) != 0, nullptr);
4558 
4559   return NS_OK;
4560 }
4561 
4562 // message flags operation - this is called from the imap protocol,
4563 // proxied over from the imap thread to the ui thread, when a flag changes
4564 NS_IMETHODIMP
NotifyMessageFlags(uint32_t aFlags,const nsACString & aKeywords,nsMsgKey aMsgKey,uint64_t aHighestModSeq)4565 nsImapMailFolder::NotifyMessageFlags(uint32_t aFlags,
4566                                      const nsACString& aKeywords,
4567                                      nsMsgKey aMsgKey,
4568                                      uint64_t aHighestModSeq) {
4569   if (NS_SUCCEEDED(GetDatabase()) && mDatabase) {
4570     bool msgDeleted = aFlags & kImapMsgDeletedFlag;
4571     if (aHighestModSeq || msgDeleted) {
4572       nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
4573       mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
4574       if (dbFolderInfo) {
4575         if (aHighestModSeq) {
4576           char intStrBuf[40];
4577           PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", aHighestModSeq);
4578           MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
4579                   ("NotifyMessageFlags(): Store highest MODSEQ=%" PRIu64
4580                    " for folder=%s",
4581                    aHighestModSeq, m_onlineFolderName.get()));
4582           dbFolderInfo->SetCharProperty(kModSeqPropertyName,
4583                                         nsDependentCString(intStrBuf));
4584         }
4585         if (msgDeleted) {
4586           uint32_t oldDeletedCount;
4587           dbFolderInfo->GetUint32Property(kDeletedHdrCountPropertyName, 0,
4588                                           &oldDeletedCount);
4589           dbFolderInfo->SetUint32Property(kDeletedHdrCountPropertyName,
4590                                           oldDeletedCount + 1);
4591         }
4592       }
4593     }
4594     nsCOMPtr<nsIMsgDBHdr> dbHdr;
4595     bool containsKey;
4596     nsresult rv = mDatabase->ContainsKey(aMsgKey, &containsKey);
4597     // if we don't have the header, don't diddle the flags.
4598     // GetMsgHdrForKey will create the header if it doesn't exist.
4599     if (NS_FAILED(rv) || !containsKey) return rv;
4600     rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(dbHdr));
4601     if (NS_SUCCEEDED(rv) && dbHdr) {
4602       uint32_t supportedUserFlags;
4603       GetSupportedUserFlags(&supportedUserFlags);
4604       NotifyMessageFlagsFromHdr(dbHdr, aMsgKey, aFlags);
4605       nsCString keywords(aKeywords);
4606       HandleCustomFlags(aMsgKey, dbHdr, supportedUserFlags, keywords);
4607     }
4608   }
4609   return NS_OK;
4610 }
4611 
4612 NS_IMETHODIMP
NotifyMessageDeleted(const char * onlineFolderName,bool deleteAllMsgs,const char * msgIdString)4613 nsImapMailFolder::NotifyMessageDeleted(const char* onlineFolderName,
4614                                        bool deleteAllMsgs,
4615                                        const char* msgIdString) {
4616   if (deleteAllMsgs) return NS_OK;
4617 
4618   if (!msgIdString) return NS_OK;
4619 
4620   nsTArray<nsMsgKey> affectedMessages;
4621   ParseUidString(msgIdString, affectedMessages);
4622 
4623   if (!ShowDeletedMessages()) {
4624     GetDatabase();
4625     NS_ENSURE_TRUE(mDatabase, NS_OK);
4626     if (!ShowDeletedMessages()) {
4627       if (!affectedMessages.IsEmpty())  // perhaps Search deleted these messages
4628       {
4629         DeleteStoreMessages(affectedMessages);
4630         mDatabase->DeleteMessages(affectedMessages, nullptr);
4631       }
4632     } else  // && !imapDeleteIsMoveToTrash // TODO: can this ever be executed?
4633       SetIMAPDeletedFlag(mDatabase, affectedMessages, false);
4634   }
4635   return NS_OK;
4636 }
4637 
ShowDeletedMessages()4638 bool nsImapMailFolder::ShowDeletedMessages() {
4639   nsresult rv;
4640   nsCOMPtr<nsIImapHostSessionList> hostSession =
4641       do_GetService(kCImapHostSessionList, &rv);
4642   NS_ENSURE_SUCCESS(rv, false);
4643 
4644   bool showDeleted = false;
4645   nsCString serverKey;
4646   GetServerKey(serverKey);
4647   hostSession->GetShowDeletedMessagesForHost(serverKey.get(), showDeleted);
4648 
4649   return showDeleted;
4650 }
4651 
DeleteIsMoveToTrash()4652 bool nsImapMailFolder::DeleteIsMoveToTrash() {
4653   nsresult err;
4654   nsCOMPtr<nsIImapHostSessionList> hostSession =
4655       do_GetService(kCImapHostSessionList, &err);
4656   NS_ENSURE_SUCCESS(err, true);
4657   bool rv = true;
4658 
4659   nsCString serverKey;
4660   GetServerKey(serverKey);
4661   hostSession->GetDeleteIsMoveToTrashForHost(serverKey.get(), rv);
4662   return rv;
4663 }
4664 
GetTrashFolder(nsIMsgFolder ** pTrashFolder)4665 nsresult nsImapMailFolder::GetTrashFolder(nsIMsgFolder** pTrashFolder) {
4666   NS_ENSURE_ARG_POINTER(pTrashFolder);
4667   nsCOMPtr<nsIMsgFolder> rootFolder;
4668   nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
4669   if (NS_SUCCEEDED(rv) && rootFolder) {
4670     rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, pTrashFolder);
4671     if (!*pTrashFolder) rv = NS_ERROR_FAILURE;
4672   }
4673   return rv;
4674 }
4675 
4676 // store nsMsgMessageFlags::IMAPDeleted in the specified mailhdr records
SetIMAPDeletedFlag(nsIMsgDatabase * mailDB,const nsTArray<nsMsgKey> & msgids,bool markDeleted)4677 void nsImapMailFolder::SetIMAPDeletedFlag(nsIMsgDatabase* mailDB,
4678                                           const nsTArray<nsMsgKey>& msgids,
4679                                           bool markDeleted) {
4680   nsresult markStatus = NS_OK;
4681   uint32_t total = msgids.Length();
4682 
4683   for (uint32_t msgIndex = 0; NS_SUCCEEDED(markStatus) && (msgIndex < total);
4684        msgIndex++)
4685     markStatus =
4686         mailDB->MarkImapDeleted(msgids[msgIndex], markDeleted, nullptr);
4687 }
4688 
4689 NS_IMETHODIMP
GetMessageSizeFromDB(const char * id,uint32_t * size)4690 nsImapMailFolder::GetMessageSizeFromDB(const char* id, uint32_t* size) {
4691   NS_ENSURE_ARG_POINTER(size);
4692 
4693   *size = 0;
4694   nsresult rv = GetDatabase();
4695   NS_ENSURE_SUCCESS(rv, rv);
4696   if (id) {
4697     nsMsgKey key = msgKeyFromInt(ParseUint64Str(id));
4698     nsCOMPtr<nsIMsgDBHdr> mailHdr;
4699     rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(mailHdr));
4700     if (NS_SUCCEEDED(rv) && mailHdr) rv = mailHdr->GetMessageSize(size);
4701   }
4702   return rv;
4703 }
4704 
4705 NS_IMETHODIMP
SetContentModified(nsIImapUrl * aImapUrl,nsImapContentModifiedType modified)4706 nsImapMailFolder::SetContentModified(nsIImapUrl* aImapUrl,
4707                                      nsImapContentModifiedType modified) {
4708   return aImapUrl->SetContentModified(modified);
4709 }
4710 
4711 NS_IMETHODIMP
GetCurMoveCopyMessageInfo(nsIImapUrl * runningUrl,PRTime * aDate,nsACString & aKeywords,uint32_t * aResult)4712 nsImapMailFolder::GetCurMoveCopyMessageInfo(nsIImapUrl* runningUrl,
4713                                             PRTime* aDate,
4714                                             nsACString& aKeywords,
4715                                             uint32_t* aResult) {
4716   nsCOMPtr<nsISupports> copyState;
4717   runningUrl->GetCopyState(getter_AddRefs(copyState));
4718   if (copyState) {
4719     nsCOMPtr<nsImapMailCopyState> mailCopyState = do_QueryInterface(copyState);
4720     uint32_t supportedFlags = 0;
4721     GetSupportedUserFlags(&supportedFlags);
4722     if (mailCopyState &&
4723         mailCopyState->m_curIndex < mailCopyState->m_messages.Length()) {
4724       nsIMsgDBHdr* message =
4725           mailCopyState->m_messages[mailCopyState->m_curIndex];
4726       nsMsgLabelValue label;
4727       message->GetFlags(aResult);
4728       if (supportedFlags & (kImapMsgSupportUserFlag | kImapMsgLabelFlags)) {
4729         message->GetLabel(&label);
4730         if (label != 0) *aResult |= label << 25;
4731       }
4732       if (aDate) message->GetDate(aDate);
4733       if (supportedFlags & kImapMsgSupportUserFlag) {
4734         // setup the custom imap keywords, which includes the message keywords
4735         // plus any junk status
4736         nsCString junkscore;
4737         message->GetStringProperty("junkscore", getter_Copies(junkscore));
4738         bool isJunk = false, isNotJunk = false;
4739         if (!junkscore.IsEmpty()) {
4740           if (junkscore.EqualsLiteral("0"))
4741             isNotJunk = true;
4742           else
4743             isJunk = true;
4744         }
4745 
4746         nsCString keywords;  // MsgFindKeyword can't use nsACString
4747         message->GetStringProperty("keywords", getter_Copies(keywords));
4748         int32_t start;
4749         int32_t length;
4750         bool hasJunk = MsgFindKeyword("junk"_ns, keywords, &start, &length);
4751         if (hasJunk && !isJunk)
4752           keywords.Cut(start, length);
4753         else if (!hasJunk && isJunk)
4754           keywords.AppendLiteral(" Junk");
4755         bool hasNonJunk =
4756             MsgFindKeyword("nonjunk"_ns, keywords, &start, &length);
4757         if (!hasNonJunk)
4758           hasNonJunk = MsgFindKeyword("notjunk"_ns, keywords, &start, &length);
4759         if (hasNonJunk && !isNotJunk)
4760           keywords.Cut(start, length);
4761         else if (!hasNonJunk && isNotJunk)
4762           keywords.AppendLiteral(" NonJunk");
4763 
4764         // Cleanup extra spaces
4765         while (!keywords.IsEmpty() && keywords.First() == ' ')
4766           keywords.Cut(0, 1);
4767         while (!keywords.IsEmpty() && keywords.Last() == ' ')
4768           keywords.Cut(keywords.Length() - 1, 1);
4769         while (!keywords.IsEmpty() && (start = keywords.Find("  "_ns)) >= 0)
4770           keywords.Cut(start, 1);
4771         aKeywords.Assign(keywords);
4772       }
4773     }
4774     // if we don't have a source header, and it's not the drafts folder,
4775     // then mark the message read, since it must be an append to the
4776     // fcc or templates folder.
4777     else if (mailCopyState) {
4778       *aResult = mailCopyState->m_newMsgFlags;
4779       if (supportedFlags & kImapMsgSupportUserFlag)
4780         aKeywords.Assign(mailCopyState->m_newMsgKeywords);
4781     }
4782   }
4783   return NS_OK;
4784 }
4785 
4786 NS_IMETHODIMP
OnStartRunningUrl(nsIURI * aUrl)4787 nsImapMailFolder::OnStartRunningUrl(nsIURI* aUrl) {
4788   NS_ASSERTION(aUrl, "sanity check - need to be be running non-null url");
4789   nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
4790   if (mailUrl) {
4791     bool updatingFolder;
4792     mailUrl->GetUpdatingFolder(&updatingFolder);
4793     m_updatingFolder = updatingFolder;
4794   }
4795   m_urlRunning = true;
4796   return NS_OK;
4797 }
4798 
4799 NS_IMETHODIMP
OnStopRunningUrl(nsIURI * aUrl,nsresult aExitCode)4800 nsImapMailFolder::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
4801   nsresult rv;
4802   bool endedOfflineDownload = false;
4803   nsImapAction imapAction = nsIImapUrl::nsImapTest;
4804   m_urlRunning = false;
4805   m_updatingFolder = false;
4806   nsCOMPtr<nsIMsgMailSession> session =
4807       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
4808   NS_ENSURE_SUCCESS(rv, rv);
4809   if (aUrl) {
4810     nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl, &rv);
4811     NS_ENSURE_SUCCESS(rv, rv);
4812     bool downloadingForOfflineUse;
4813     imapUrl->GetStoreResultsOffline(&downloadingForOfflineUse);
4814     bool hasSemaphore = false;
4815     // if we have the folder locked, clear it.
4816     TestSemaphore(static_cast<nsIMsgFolder*>(this), &hasSemaphore);
4817     if (hasSemaphore) ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
4818     if (downloadingForOfflineUse) {
4819       endedOfflineDownload = true;
4820       EndOfflineDownload();
4821     }
4822     nsCOMPtr<nsIMsgWindow> msgWindow;
4823     nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
4824     bool folderOpen = false;
4825     if (mailUrl) mailUrl->GetMsgWindow(getter_AddRefs(msgWindow));
4826     if (session) session->IsFolderOpenInWindow(this, &folderOpen);
4827 #ifdef DEBUG_bienvenu
4828     printf("stop running url %s\n", aUrl->GetSpecOrDefault().get());
4829 #endif
4830 
4831     if (imapUrl) {
4832       DisplayStatusMsg(imapUrl, EmptyString());
4833       imapUrl->GetImapAction(&imapAction);
4834       if (imapAction == nsIImapUrl::nsImapMsgFetch ||
4835           imapAction == nsIImapUrl::nsImapMsgDownloadForOffline) {
4836         ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
4837         if (!endedOfflineDownload) EndOfflineDownload();
4838       }
4839 
4840       // Notify move, copy or delete (online operations)
4841       // Not sure whether nsImapDeleteMsg is even used, deletes in all three
4842       // models use nsImapAddMsgFlags.
4843       nsCOMPtr<nsIMsgFolderNotificationService> notifier(
4844           do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
4845       if (notifier && m_copyState) {
4846         if (imapAction == nsIImapUrl::nsImapOnlineMove) {
4847           notifier->NotifyMsgsMoveCopyCompleted(true, m_copyState->m_messages,
4848                                                 this, {});
4849         } else if (imapAction == nsIImapUrl::nsImapOnlineCopy) {
4850           notifier->NotifyMsgsMoveCopyCompleted(false, m_copyState->m_messages,
4851                                                 this, {});
4852         } else if (imapAction == nsIImapUrl::nsImapDeleteMsg) {
4853           notifier->NotifyMsgsDeleted(m_copyState->m_messages);
4854         }
4855       }
4856 
4857       switch (imapAction) {
4858         case nsIImapUrl::nsImapDeleteMsg:
4859         case nsIImapUrl::nsImapOnlineMove:
4860         case nsIImapUrl::nsImapOnlineCopy:
4861           if (NS_SUCCEEDED(aExitCode)) {
4862             if (folderOpen)
4863               UpdateFolder(msgWindow);
4864             else
4865               UpdatePendingCounts();
4866           }
4867 
4868           if (m_copyState) {
4869             nsCOMPtr<nsIMsgFolder> srcFolder =
4870                 do_QueryInterface(m_copyState->m_srcSupport, &rv);
4871             if (m_copyState->m_isMove && !m_copyState->m_isCrossServerOp) {
4872               if (NS_SUCCEEDED(aExitCode)) {
4873                 nsCOMPtr<nsIMsgDatabase> srcDB;
4874                 if (srcFolder)
4875                   rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
4876                 if (NS_SUCCEEDED(rv) && srcDB) {
4877                   RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
4878                   nsTArray<nsMsgKey> srcKeyArray;
4879                   if (m_copyState->m_allowUndo) {
4880                     msgTxn = m_copyState->m_undoMsgTxn;
4881                     if (msgTxn) msgTxn->GetSrcKeyArray(srcKeyArray);
4882                   } else {
4883                     nsAutoCString messageIds;
4884                     rv = BuildIdsAndKeyArray(m_copyState->m_messages,
4885                                              messageIds, srcKeyArray);
4886                     NS_ENSURE_SUCCESS(rv, rv);
4887                   }
4888 
4889                   if (!ShowDeletedMessages()) {
4890                     // We only reach here for same-server operations
4891                     // (!m_copyState->m_isCrossServerOp in if above), so we can
4892                     // assume that the src is also imap that uses offline
4893                     // storage.
4894                     DeleteStoreMessages(srcKeyArray, srcFolder);
4895                     srcDB->DeleteMessages(srcKeyArray, nullptr);
4896                   } else
4897                     MarkMessagesImapDeleted(&srcKeyArray, true, srcDB);
4898                 }
4899                 srcFolder->EnableNotifications(allMessageCountNotifications,
4900                                                true);
4901                 // even if we're showing deleted messages,
4902                 // we still need to notify FE so it will show the imap deleted
4903                 // flag
4904                 srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
4905                 // is there a way to see that we think we have new msgs?
4906                 nsCOMPtr<nsIPrefBranch> prefBranch(
4907                     do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
4908                 if (NS_SUCCEEDED(rv)) {
4909                   bool showPreviewText;
4910                   prefBranch->GetBoolPref("mail.biff.alert.show_preview",
4911                                           &showPreviewText);
4912                   // if we're showing preview text, update ourselves if we got a
4913                   // new unread message copied so that we can download the new
4914                   // headers and have a chance to preview the msg bodies.
4915                   if (!folderOpen && showPreviewText &&
4916                       m_copyState->m_unreadCount > 0 &&
4917                       !(mFlags &
4918                         (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
4919                     UpdateFolder(msgWindow);
4920                 }
4921               } else {
4922                 srcFolder->EnableNotifications(allMessageCountNotifications,
4923                                                true);
4924                 srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
4925               }
4926             }
4927             if (m_copyState->m_msgWindow &&
4928                 m_copyState->m_undoMsgTxn &&  // may be null from filters
4929                 NS_SUCCEEDED(
4930                     aExitCode))  // we should do this only if move/copy succeeds
4931             {
4932               nsCOMPtr<nsITransactionManager> txnMgr;
4933               m_copyState->m_msgWindow->GetTransactionManager(
4934                   getter_AddRefs(txnMgr));
4935               if (txnMgr) {
4936                 RefPtr<nsImapMoveCopyMsgTxn> txn = m_copyState->m_undoMsgTxn;
4937                 mozilla::DebugOnly<nsresult> rv2 = txnMgr->DoTransaction(txn);
4938                 NS_ASSERTION(NS_SUCCEEDED(rv2), "doing transaction failed");
4939               }
4940             }
4941             // nsImapUrl can hold a pointer to our m_copyState, so force a
4942             // release here (see Bug 1586494).
4943             imapUrl->SetCopyState(nullptr);
4944             (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
4945           }
4946 
4947           // we're the dest folder of a move/copy - if we're not open in the ui,
4948           // then we should clear our nsMsgDatabase pointer. Otherwise, the db
4949           // would be open until the user selected it and then selected another
4950           // folder. but don't do this for the trash or inbox - we'll leave them
4951           // open
4952           if (!folderOpen &&
4953               !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
4954             SetMsgDatabase(nullptr);
4955           break;
4956         case nsIImapUrl::nsImapSubtractMsgFlags: {
4957           // this isn't really right - we'd like to know we were
4958           // deleting a message to start with, but it probably
4959           // won't do any harm.
4960           imapMessageFlagsType flags = 0;
4961           imapUrl->GetMsgFlags(&flags);
4962           // we need to subtract the delete flag in db only in case when we show
4963           // deleted msgs
4964           if (flags & kImapMsgDeletedFlag && ShowDeletedMessages()) {
4965             nsCOMPtr<nsIMsgDatabase> db;
4966             rv = GetMsgDatabase(getter_AddRefs(db));
4967             if (NS_SUCCEEDED(rv) && db) {
4968               nsTArray<nsMsgKey> keyArray;
4969               nsCString keyString;
4970               imapUrl->GetListOfMessageIds(keyString);
4971               ParseUidString(keyString.get(), keyArray);
4972               MarkMessagesImapDeleted(&keyArray, false, db);
4973               db->Commit(nsMsgDBCommitType::kLargeCommit);
4974             }
4975           }
4976         } break;
4977         case nsIImapUrl::nsImapAddMsgFlags: {
4978           imapMessageFlagsType flags = 0;
4979           imapUrl->GetMsgFlags(&flags);
4980           if (flags & kImapMsgDeletedFlag) {
4981             // we need to delete headers from db only when we don't show deleted
4982             // msgs
4983             if (!ShowDeletedMessages()) {
4984               nsCOMPtr<nsIMsgDatabase> db;
4985               rv = GetMsgDatabase(getter_AddRefs(db));
4986               if (NS_SUCCEEDED(rv) && db) {
4987                 nsTArray<nsMsgKey> keyArray;
4988                 nsCString keyString;
4989                 imapUrl->GetListOfMessageIds(keyString);
4990                 ParseUidString(keyString.get(), keyArray);
4991 
4992                 // For pluggable stores that do not support compaction, we need
4993                 // to delete the messages now.
4994                 bool supportsCompaction = false;
4995                 nsCOMPtr<nsIMsgPluggableStore> offlineStore;
4996                 (void)GetMsgStore(getter_AddRefs(offlineStore));
4997                 if (offlineStore)
4998                   offlineStore->GetSupportsCompaction(&supportsCompaction);
4999 
5000                 nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrs;
5001                 if (notifier || !supportsCompaction) {
5002                   MsgGetHeadersFromKeys(db, keyArray, msgHdrs);
5003                 }
5004 
5005                 // Notify listeners of delete.
5006                 if (notifier && !msgHdrs.IsEmpty()) {
5007                   // XXX Currently, the DeleteMessages below gets executed twice
5008                   // on deletes. Once in DeleteMessages, once here. The second
5009                   // time, it silently fails to delete. This is why we're also
5010                   // checking whether the array is empty.
5011                   notifier->NotifyMsgsDeleted(msgHdrs);
5012                 }
5013 
5014                 if (!supportsCompaction && !msgHdrs.IsEmpty())
5015                   DeleteStoreMessages(msgHdrs);
5016 
5017                 db->DeleteMessages(keyArray, nullptr);
5018                 db->SetSummaryValid(true);
5019                 db->Commit(nsMsgDBCommitType::kLargeCommit);
5020               }
5021             }
5022           }
5023         } break;
5024         case nsIImapUrl::nsImapAppendMsgFromFile:
5025         case nsIImapUrl::nsImapAppendDraftFromFile:
5026           if (m_copyState) {
5027             if (NS_SUCCEEDED(aExitCode)) {
5028               UpdatePendingCounts();
5029 
5030               m_copyState->m_curIndex++;
5031               if (m_copyState->m_curIndex >= m_copyState->m_messages.Length()) {
5032                 nsCOMPtr<nsIUrlListener> saveUrlListener = m_urlListener;
5033                 if (folderOpen) {
5034                   // This gives a way for the caller to get notified
5035                   // when the UpdateFolder url is done.
5036                   // (if the nsIMsgCopyServiceListener also implements
5037                   // nsIUrlListener)
5038                   if (m_copyState->m_listener)
5039                     m_urlListener = do_QueryInterface(m_copyState->m_listener);
5040                 }
5041                 if (m_copyState->m_msgWindow && m_copyState->m_undoMsgTxn) {
5042                   nsCOMPtr<nsITransactionManager> txnMgr;
5043                   m_copyState->m_msgWindow->GetTransactionManager(
5044                       getter_AddRefs(txnMgr));
5045                   if (txnMgr) {
5046                     RefPtr<nsImapMoveCopyMsgTxn> txn =
5047                         m_copyState->m_undoMsgTxn;
5048                     txnMgr->DoTransaction(txn);
5049                   }
5050                 }
5051                 (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
5052                 if (folderOpen ||
5053                     imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
5054                   UpdateFolderWithListener(msgWindow, m_urlListener);
5055                   m_urlListener = saveUrlListener;
5056                 }
5057               }
5058             } else {
5059               // clear the copyState if copy has failed
5060               (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
5061             }
5062           }
5063           break;
5064         case nsIImapUrl::nsImapMoveFolderHierarchy:
5065           if (m_copyState)  // delete folder gets here, but w/o an m_copyState
5066           {
5067             nsCOMPtr<nsIMsgCopyService> copyService =
5068                 do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
5069             NS_ENSURE_SUCCESS(rv, rv);
5070             nsCOMPtr<nsIMsgFolder> srcFolder =
5071                 do_QueryInterface(m_copyState->m_srcSupport);
5072             if (srcFolder) {
5073               copyService->NotifyCompletion(m_copyState->m_srcSupport, this,
5074                                             aExitCode);
5075             }
5076             m_copyState = nullptr;
5077           }
5078           break;
5079         case nsIImapUrl::nsImapRenameFolder:
5080           if (NS_FAILED(aExitCode)) {
5081             NotifyFolderEvent(kRenameCompleted);
5082           }
5083           break;
5084         case nsIImapUrl::nsImapDeleteAllMsgs:
5085           if (NS_SUCCEEDED(aExitCode)) {
5086             if (folderOpen)
5087               UpdateFolder(msgWindow);
5088             else {
5089               ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
5090               ChangeNumPendingUnread(-mNumPendingUnreadMessages);
5091               m_numServerUnseenMessages = 0;
5092             }
5093           }
5094           break;
5095         case nsIImapUrl::nsImapListFolder:
5096           if (NS_SUCCEEDED(aExitCode)) {
5097             // listing folder will open db; don't leave the db open.
5098             SetMsgDatabase(nullptr);
5099             if (!m_verifiedAsOnlineFolder) {
5100               // If folder is not verified, we remove it.
5101               nsCOMPtr<nsIMsgFolder> parent;
5102               rv = GetParent(getter_AddRefs(parent));
5103               if (NS_SUCCEEDED(rv) && parent) {
5104                 nsCOMPtr<nsIMsgImapMailFolder> imapParent =
5105                     do_QueryInterface(parent);
5106                 if (imapParent) this->RemoveLocalSelf();
5107               }
5108             }
5109           }
5110           break;
5111         case nsIImapUrl::nsImapRefreshFolderUrls:
5112           // we finished getting an admin url for the folder.
5113           if (!m_adminUrl.IsEmpty()) FolderPrivileges(msgWindow);
5114           break;
5115         case nsIImapUrl::nsImapCreateFolder:
5116           if (NS_FAILED(aExitCode))  // if success notification already done
5117           {
5118             NotifyFolderEvent(kFolderCreateFailed);
5119           }
5120           break;
5121         case nsIImapUrl::nsImapSubscribe:
5122           if (NS_SUCCEEDED(aExitCode) && msgWindow) {
5123             nsCString canonicalFolderName;
5124             imapUrl->CreateCanonicalSourceFolderPathString(
5125                 getter_Copies(canonicalFolderName));
5126             nsCOMPtr<nsIMsgFolder> rootFolder;
5127             nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
5128             if (NS_SUCCEEDED(rv) && rootFolder) {
5129               nsCOMPtr<nsIMsgImapMailFolder> imapRoot =
5130                   do_QueryInterface(rootFolder);
5131               if (imapRoot) {
5132                 nsCOMPtr<nsIMsgImapMailFolder> foundFolder;
5133                 rv = imapRoot->FindOnlineSubFolder(canonicalFolderName,
5134                                                    getter_AddRefs(foundFolder));
5135                 if (NS_SUCCEEDED(rv) && foundFolder) {
5136                   nsCString uri;
5137                   nsCOMPtr<nsIMsgFolder> msgFolder =
5138                       do_QueryInterface(foundFolder);
5139                   if (msgFolder) {
5140                     msgFolder->GetURI(uri);
5141                     nsCOMPtr<nsIMsgWindowCommands> windowCommands;
5142                     msgWindow->GetWindowCommands(
5143                         getter_AddRefs(windowCommands));
5144                     if (windowCommands) windowCommands->SelectFolder(uri);
5145                   }
5146                 }
5147               }
5148             }
5149           }
5150           break;
5151         case nsIImapUrl::nsImapExpungeFolder:
5152           m_expunging = false;
5153           break;
5154         default:
5155           break;
5156       }
5157     }
5158     // give base class a chance to send folder loaded notification...
5159     rv = nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
5160   }
5161   // if we're not running a url, we must not be getting new mail.
5162   SetGettingNewMessages(false);
5163   // don't send OnStopRunning notification if still compacting offline store.
5164   if (m_urlListener && (imapAction != nsIImapUrl::nsImapExpungeFolder ||
5165                         !m_compactingOfflineStore)) {
5166     nsCOMPtr<nsIUrlListener> saveListener = m_urlListener;
5167     m_urlListener = nullptr;
5168     saveListener->OnStopRunningUrl(aUrl, aExitCode);
5169   }
5170   return rv;
5171 }
5172 
UpdatePendingCounts()5173 void nsImapMailFolder::UpdatePendingCounts() {
5174   if (m_copyState) {
5175     int32_t delta =
5176         m_copyState->m_isCrossServerOp ? 1 : m_copyState->m_messages.Length();
5177     if (!m_copyState->m_selectedState && m_copyState->m_messages.IsEmpty()) {
5178       // special case from CopyFileMessage():
5179       // - copied a single message in from a file
5180       // - no previously-existing messages are involved
5181       delta = 1;
5182     }
5183     ChangePendingTotal(delta);
5184 
5185     // count the moves that were unread
5186     int numUnread = m_copyState->m_unreadCount;
5187     if (numUnread) {
5188       m_numServerUnseenMessages +=
5189           numUnread;  // adjust last status count by this delta.
5190       ChangeNumPendingUnread(numUnread);
5191     }
5192     SummaryChanged();
5193   }
5194 }
5195 
5196 NS_IMETHODIMP
ClearFolderRights()5197 nsImapMailFolder::ClearFolderRights() {
5198   SetFolderNeedsACLListed(false);
5199   delete m_folderACL;
5200   m_folderACL = new nsMsgIMAPFolderACL(this);
5201   return NS_OK;
5202 }
5203 
5204 NS_IMETHODIMP
AddFolderRights(const nsACString & userName,const nsACString & rights)5205 nsImapMailFolder::AddFolderRights(const nsACString& userName,
5206                                   const nsACString& rights) {
5207   SetFolderNeedsACLListed(false);
5208   GetFolderACL()->SetFolderRightsForUser(userName, rights);
5209   return NS_OK;
5210 }
5211 
5212 NS_IMETHODIMP
RefreshFolderRights()5213 nsImapMailFolder::RefreshFolderRights() {
5214   if (GetFolderACL()->GetIsFolderShared())
5215     SetFlag(nsMsgFolderFlags::PersonalShared);
5216   else
5217     ClearFlag(nsMsgFolderFlags::PersonalShared);
5218   return NS_OK;
5219 }
5220 
5221 NS_IMETHODIMP
SetCopyResponseUid(const char * msgIdString,nsIImapUrl * aUrl)5222 nsImapMailFolder::SetCopyResponseUid(const char* msgIdString,
5223                                      nsIImapUrl* aUrl) {  // CopyMessages() only
5224   nsresult rv = NS_OK;
5225   RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
5226   nsCOMPtr<nsISupports> copyState;
5227 
5228   if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState));
5229 
5230   if (copyState) {
5231     nsCOMPtr<nsImapMailCopyState> mailCopyState =
5232         do_QueryInterface(copyState, &rv);
5233     if (NS_FAILED(rv)) return rv;
5234     if (mailCopyState->m_undoMsgTxn) msgTxn = mailCopyState->m_undoMsgTxn;
5235   } else if (aUrl && m_pendingOfflineMoves.Length()) {
5236     nsCString urlSourceMsgIds, undoTxnSourceMsgIds;
5237     aUrl->GetListOfMessageIds(urlSourceMsgIds);
5238     RefPtr<nsImapMoveCopyMsgTxn> imapUndo = m_pendingOfflineMoves[0];
5239     if (imapUndo) {
5240       imapUndo->GetSrcMsgIds(undoTxnSourceMsgIds);
5241       if (undoTxnSourceMsgIds.Equals(urlSourceMsgIds)) msgTxn = imapUndo;
5242       // ### we should handle batched moves, but lets keep it simple for a2.
5243       m_pendingOfflineMoves.Clear();
5244     }
5245   }
5246   if (msgTxn) msgTxn->SetCopyResponseUid(msgIdString);
5247   return NS_OK;
5248 }
5249 
5250 NS_IMETHODIMP
StartMessage(nsIMsgMailNewsUrl * aUrl)5251 nsImapMailFolder::StartMessage(nsIMsgMailNewsUrl* aUrl) {
5252   nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
5253   nsCOMPtr<nsISupports> copyState;
5254   NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);
5255 
5256   imapUrl->GetCopyState(getter_AddRefs(copyState));
5257   if (copyState) {
5258     nsCOMPtr<nsICopyMessageStreamListener> listener =
5259         do_QueryInterface(copyState);
5260     if (listener) listener->StartMessage();
5261   }
5262   return NS_OK;
5263 }
5264 
5265 NS_IMETHODIMP
EndMessage(nsIMsgMailNewsUrl * aUrl,nsMsgKey uidOfMessage)5266 nsImapMailFolder::EndMessage(nsIMsgMailNewsUrl* aUrl, nsMsgKey uidOfMessage) {
5267   nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
5268   nsCOMPtr<nsISupports> copyState;
5269   NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);
5270   imapUrl->GetCopyState(getter_AddRefs(copyState));
5271   if (copyState) {
5272     nsCOMPtr<nsICopyMessageStreamListener> listener =
5273         do_QueryInterface(copyState);
5274     if (listener) listener->EndMessage(uidOfMessage);
5275   }
5276   return NS_OK;
5277 }
5278 
5279 #define WHITESPACE " \015\012"  // token delimiter
5280 
5281 NS_IMETHODIMP
NotifySearchHit(nsIMsgMailNewsUrl * aUrl,const char * searchHitLine)5282 nsImapMailFolder::NotifySearchHit(nsIMsgMailNewsUrl* aUrl,
5283                                   const char* searchHitLine) {
5284   NS_ENSURE_ARG_POINTER(aUrl);
5285   nsresult rv = GetDatabase();
5286   NS_ENSURE_SUCCESS(rv, rv);
5287 
5288   // expect search results in the form of "* SEARCH <hit> <hit> ..."
5289   // expect search results in the form of "* SEARCH <hit> <hit> ..."
5290   nsCString tokenString(searchHitLine);
5291   char* currentPosition = PL_strcasestr(tokenString.get(), "SEARCH");
5292   if (currentPosition) {
5293     currentPosition += strlen("SEARCH");
5294     bool shownUpdateAlert = false;
5295     char* hitUidToken = NS_strtok(WHITESPACE, &currentPosition);
5296     while (hitUidToken) {
5297       long naturalLong;  // %l is 64 bits on OSF1
5298       sscanf(hitUidToken, "%ld", &naturalLong);
5299       nsMsgKey hitUid = (nsMsgKey)naturalLong;
5300 
5301       nsCOMPtr<nsIMsgDBHdr> hitHeader;
5302       rv = mDatabase->GetMsgHdrForKey(hitUid, getter_AddRefs(hitHeader));
5303       if (NS_SUCCEEDED(rv) && hitHeader) {
5304         nsCOMPtr<nsIMsgSearchSession> searchSession;
5305         nsCOMPtr<nsIMsgSearchAdapter> searchAdapter;
5306         aUrl->GetSearchSession(getter_AddRefs(searchSession));
5307         if (searchSession) {
5308           searchSession->GetRunningAdapter(getter_AddRefs(searchAdapter));
5309           if (searchAdapter) searchAdapter->AddResultElement(hitHeader);
5310         }
5311       } else if (!shownUpdateAlert) {
5312       }
5313 
5314       hitUidToken = NS_strtok(WHITESPACE, &currentPosition);
5315     }
5316   }
5317   return NS_OK;
5318 }
5319 
5320 NS_IMETHODIMP
SetAppendMsgUid(nsMsgKey aKey,nsIImapUrl * aUrl)5321 nsImapMailFolder::SetAppendMsgUid(nsMsgKey aKey, nsIImapUrl* aUrl) {
5322   nsresult rv;
5323   nsCOMPtr<nsISupports> copyState;
5324   if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState));
5325   if (copyState) {
5326     nsCOMPtr<nsImapMailCopyState> mailCopyState =
5327         do_QueryInterface(copyState, &rv);
5328     if (NS_FAILED(rv)) return rv;
5329 
5330     if (mailCopyState->m_undoMsgTxn)  // CopyMessages()
5331     {
5332       RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
5333       msgTxn = mailCopyState->m_undoMsgTxn;
5334       msgTxn->AddDstKey(aKey);
5335     } else if (mailCopyState->m_listener)  // CopyFileMessage();
5336                                            // Draft/Template goes here
5337     {
5338       mailCopyState->m_appendUID = aKey;
5339       mailCopyState->m_listener->SetMessageKey(aKey);
5340     }
5341   }
5342   return NS_OK;
5343 }
5344 
5345 NS_IMETHODIMP
GetMessageId(nsIImapUrl * aUrl,nsACString & messageId)5346 nsImapMailFolder::GetMessageId(nsIImapUrl* aUrl, nsACString& messageId) {
5347   nsresult rv = NS_OK;
5348   nsCOMPtr<nsISupports> copyState;
5349 
5350   if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState));
5351   if (copyState) {
5352     nsCOMPtr<nsImapMailCopyState> mailCopyState =
5353         do_QueryInterface(copyState, &rv);
5354     if (NS_FAILED(rv)) return rv;
5355     if (mailCopyState->m_listener)
5356       rv = mailCopyState->m_listener->GetMessageId(messageId);
5357   }
5358   if (NS_SUCCEEDED(rv) && messageId.Length() > 0) {
5359     if (messageId.First() == '<') messageId.Cut(0, 1);
5360     if (messageId.Last() == '>') messageId.SetLength(messageId.Length() - 1);
5361   }
5362   return rv;
5363 }
5364 
5365 NS_IMETHODIMP
HeaderFetchCompleted(nsIImapProtocol * aProtocol)5366 nsImapMailFolder::HeaderFetchCompleted(nsIImapProtocol* aProtocol) {
5367   nsCOMPtr<nsIMsgWindow>
5368       msgWindow;  // we might need this for the filter plugins.
5369   if (mBackupDatabase) RemoveBackupMsgDatabase();
5370 
5371   SetSizeOnDisk(mFolderSize);
5372   int32_t numNewBiffMsgs = 0;
5373   if (m_performingBiff) GetNumNewMessages(false, &numNewBiffMsgs);
5374 
5375   bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
5376   PlaybackCoalescedOperations();
5377   if (aProtocol) {
5378     // check if we should download message bodies because it's the inbox and
5379     // the server is specified as one where where we download msg bodies
5380     // automatically. Or if we autosyncing all offline folders.
5381     nsCOMPtr<nsIImapIncomingServer> imapServer;
5382     GetImapIncomingServer(getter_AddRefs(imapServer));
5383 
5384     bool autoDownloadNewHeaders = false;
5385     bool autoSyncOfflineStores = false;
5386 
5387     if (imapServer) {
5388       imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
5389       imapServer->GetDownloadBodiesOnGetNewMail(&autoDownloadNewHeaders);
5390       if (m_filterListRequiresBody) autoDownloadNewHeaders = true;
5391     }
5392     bool notifiedBodies = false;
5393     if (m_downloadingFolderForOfflineUse || autoSyncOfflineStores ||
5394         autoDownloadNewHeaders) {
5395       nsTArray<nsMsgKey> keysToDownload;
5396       GetBodysToDownload(&keysToDownload);
5397       // this is the case when DownloadAllForOffline is called.
5398       if (!keysToDownload.IsEmpty() &&
5399           (m_downloadingFolderForOfflineUse || autoDownloadNewHeaders)) {
5400         notifiedBodies = true;
5401         aProtocol->NotifyBodysToDownload(keysToDownload);
5402       } else {
5403         // create auto-sync state object lazily
5404         InitAutoSyncState();
5405 
5406         // make enough room for new downloads
5407         m_autoSyncStateObj->ManageStorageSpace();
5408         m_autoSyncStateObj->SetServerCounts(
5409             m_numServerTotalMessages, m_numServerRecentMessages,
5410             m_numServerUnseenMessages, m_nextUID);
5411         m_autoSyncStateObj->OnNewHeaderFetchCompleted(keysToDownload);
5412       }
5413     }
5414     if (!notifiedBodies) {
5415       nsTArray<nsMsgKey> noBodies;
5416       aProtocol->NotifyBodysToDownload(noBodies);
5417     }
5418 
5419     nsCOMPtr<nsIURI> runningUri;
5420     aProtocol->GetRunningUrl(getter_AddRefs(runningUri));
5421     if (runningUri) {
5422       nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(runningUri);
5423       if (mailnewsUrl) mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
5424     }
5425   }
5426 
5427   // delay calling plugins if filter application is also delayed
5428   if (!m_filterListRequiresBody) {
5429     bool filtersRun;
5430     CallFilterPlugins(msgWindow, &filtersRun);
5431     if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
5432         (!pendingMoves || !ShowPreviewText())) {
5433       // If we are performing biff for this folder, tell the
5434       // stand-alone biff about the new high water mark
5435       // We must ensure that the server knows that we are performing biff.
5436       // Otherwise the stand-alone biff won't fire.
5437       nsCOMPtr<nsIMsgIncomingServer> server;
5438       if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
5439         server->SetPerformingBiff(true);
5440 
5441       SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
5442       if (server) server->SetPerformingBiff(false);
5443       m_performingBiff = false;
5444     }
5445 
5446     if (m_filterList) (void)m_filterList->FlushLogIfNecessary();
5447   }
5448 
5449   return NS_OK;
5450 }
5451 
5452 NS_IMETHODIMP
SetBiffStateAndUpdate(nsMsgBiffState biffState)5453 nsImapMailFolder::SetBiffStateAndUpdate(nsMsgBiffState biffState) {
5454   SetBiffState(biffState);
5455   return NS_OK;
5456 }
5457 
5458 NS_IMETHODIMP
GetUidValidity(int32_t * uidValidity)5459 nsImapMailFolder::GetUidValidity(int32_t* uidValidity) {
5460   NS_ENSURE_ARG(uidValidity);
5461   if ((int32_t)m_uidValidity == kUidUnknown) {
5462     nsCOMPtr<nsIMsgDatabase> db;
5463     nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
5464     (void)GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
5465                                getter_AddRefs(db));
5466     if (db) db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
5467 
5468     if (dbFolderInfo)
5469       dbFolderInfo->GetImapUidValidity((int32_t*)&m_uidValidity);
5470   }
5471   *uidValidity = m_uidValidity;
5472   return NS_OK;
5473 }
5474 
5475 NS_IMETHODIMP
SetUidValidity(int32_t uidValidity)5476 nsImapMailFolder::SetUidValidity(int32_t uidValidity) {
5477   m_uidValidity = uidValidity;
5478   return NS_OK;
5479 }
5480 
5481 NS_IMETHODIMP
FillInFolderProps(nsIMsgImapFolderProps * aFolderProps)5482 nsImapMailFolder::FillInFolderProps(nsIMsgImapFolderProps* aFolderProps) {
5483   NS_ENSURE_ARG(aFolderProps);
5484   const char* folderTypeStringID;
5485   const char* folderTypeDescStringID = nullptr;
5486   const char* folderQuotaStatusStringID;
5487   nsString folderType;
5488   nsString folderTypeDesc;
5489   nsString folderQuotaStatusDesc;
5490   nsCOMPtr<nsIStringBundle> bundle;
5491   nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
5492   NS_ENSURE_SUCCESS(rv, rv);
5493 
5494   nsCOMPtr<nsIImapIncomingServer> imapServer;
5495   rv = GetImapIncomingServer(getter_AddRefs(imapServer));
5496   // if for some bizarre reason this fails, we'll still fall through to the
5497   // normal sharing code
5498   if (NS_SUCCEEDED(rv)) {
5499     // get the latest committed imap capabilities bit mask.
5500     eIMAPCapabilityFlags capability = kCapabilityUndefined;
5501     imapServer->GetCapability(&capability);
5502     bool haveACL = capability & kACLCapability;
5503     bool haveQuota = capability & kQuotaCapability;
5504 
5505     // Figure out what to display in the Quota tab of the folder properties.
5506     // Does the server support quotas? This depends on the latest imap
5507     // CAPABILITY response.
5508     if (haveQuota) {
5509       // Have quota capability. Have we asked the server for quota information?
5510       if (m_folderQuotaCommandIssued) {
5511         // Has the server replied with all the quota info?
5512         if (m_folderQuotaDataIsValid) {
5513           if (!m_folderQuota.IsEmpty()) {
5514             // If so, set quota data to show in the quota tab
5515             folderQuotaStatusStringID = nullptr;
5516             aFolderProps->SetQuotaData(m_folderQuota);
5517           } else {
5518             // The server reported no quota limits on this folder.
5519             folderQuotaStatusStringID = "imapQuotaStatusNoQuota2";
5520           }
5521         } else {
5522           // The getquotaroot command was sent to the server but the complete
5523           // response was not yet received when the folder properties were
5524           // requested. This is rare. Request the folder properties again to
5525           // obtain the quota data.
5526           folderQuotaStatusStringID = "imapQuotaStatusInProgress";
5527         }
5528       } else {
5529         // The folder is not open, so no quota information is available
5530         folderQuotaStatusStringID = "imapQuotaStatusFolderNotOpen";
5531       }
5532     } else {
5533       // Either the server doesn't support quotas, or we don't know if it does
5534       // (e.g., because we don't have a connection yet). If the latter, we fall
5535       // back to saying that no information is available because the folder is
5536       // not yet open.
5537       folderQuotaStatusStringID = (capability == kCapabilityUndefined)
5538                                       ? "imapQuotaStatusFolderNotOpen"
5539                                       : "imapQuotaStatusNotSupported";
5540     }
5541 
5542     if (!folderQuotaStatusStringID) {
5543       // Display quota data
5544       aFolderProps->ShowQuotaData(true);
5545     } else {
5546       // Hide quota data and show reason why it is not available
5547       aFolderProps->ShowQuotaData(false);
5548 
5549       rv = IMAPGetStringByName(folderQuotaStatusStringID,
5550                                getter_Copies(folderQuotaStatusDesc));
5551       if (NS_SUCCEEDED(rv)) aFolderProps->SetQuotaStatus(folderQuotaStatusDesc);
5552     }
5553 
5554     // See if the server supports ACL.
5555     // If not, just set the folder description to a string that says
5556     // the server doesn't support sharing, and return.
5557     if (!haveACL) {
5558       rv = IMAPGetStringByName("imapServerDoesntSupportAcl",
5559                                getter_Copies(folderTypeDesc));
5560       if (NS_SUCCEEDED(rv))
5561         aFolderProps->SetFolderTypeDescription(folderTypeDesc);
5562       aFolderProps->ServerDoesntSupportACL();
5563       return NS_OK;
5564     }
5565   }
5566   if (mFlags & nsMsgFolderFlags::ImapPublic) {
5567     folderTypeStringID = "imapPublicFolderTypeName";
5568     folderTypeDescStringID = "imapPublicFolderTypeDescription";
5569   } else if (mFlags & nsMsgFolderFlags::ImapOtherUser) {
5570     folderTypeStringID = "imapOtherUsersFolderTypeName";
5571     nsCString owner;
5572     nsString uniOwner;
5573     GetFolderOwnerUserName(owner);
5574     if (owner.IsEmpty()) {
5575       rv = IMAPGetStringByName(folderTypeStringID, getter_Copies(uniOwner));
5576       // Another user's folder, for which we couldn't find an owner name
5577       NS_ASSERTION(false, "couldn't get owner name for other user's folder");
5578     } else {
5579       // is this right? It doesn't leak, does it?
5580       CopyUTF8toUTF16(owner, uniOwner);
5581     }
5582     AutoTArray<nsString, 1> params = {uniOwner};
5583     rv = bundle->FormatStringFromName("imapOtherUsersFolderTypeDescription",
5584                                       params, folderTypeDesc);
5585   } else if (GetFolderACL()->GetIsFolderShared()) {
5586     folderTypeStringID = "imapPersonalSharedFolderTypeName";
5587     folderTypeDescStringID = "imapPersonalSharedFolderTypeDescription";
5588   } else {
5589     folderTypeStringID = "imapPersonalSharedFolderTypeName";
5590     folderTypeDescStringID = "imapPersonalFolderTypeDescription";
5591   }
5592 
5593   rv = IMAPGetStringByName(folderTypeStringID, getter_Copies(folderType));
5594   if (NS_SUCCEEDED(rv)) aFolderProps->SetFolderType(folderType);
5595 
5596   if (folderTypeDesc.IsEmpty() && folderTypeDescStringID)
5597     rv = IMAPGetStringByName(folderTypeDescStringID,
5598                              getter_Copies(folderTypeDesc));
5599   if (!folderTypeDesc.IsEmpty())
5600     aFolderProps->SetFolderTypeDescription(folderTypeDesc);
5601 
5602   nsString rightsString;
5603   rv = CreateACLRightsStringForFolder(rightsString);
5604   if (NS_SUCCEEDED(rv)) aFolderProps->SetFolderPermissions(rightsString);
5605   return NS_OK;
5606 }
5607 
SetAclFlags(uint32_t aclFlags)5608 NS_IMETHODIMP nsImapMailFolder::SetAclFlags(uint32_t aclFlags) {
5609   nsresult rv = NS_OK;
5610   if (m_aclFlags != aclFlags) {
5611     nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
5612     bool dbWasOpen = (mDatabase != nullptr);
5613     rv = GetDatabase();
5614 
5615     m_aclFlags = aclFlags;
5616     if (mDatabase) {
5617       rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
5618       if (NS_SUCCEEDED(rv) && dbFolderInfo)
5619         dbFolderInfo->SetUint32Property("aclFlags", aclFlags);
5620       // if setting the acl flags caused us to open the db, release the ref
5621       // because on startup, we might get acl on all folders,which will
5622       // leave a lot of db's open.
5623       if (!dbWasOpen) {
5624         mDatabase->Close(true /* commit changes */);
5625         mDatabase = nullptr;
5626       }
5627     }
5628   }
5629   return rv;
5630 }
5631 
GetAclFlags(uint32_t * aclFlags)5632 NS_IMETHODIMP nsImapMailFolder::GetAclFlags(uint32_t* aclFlags) {
5633   NS_ENSURE_ARG_POINTER(aclFlags);
5634   nsresult rv;
5635   ReadDBFolderInfo(false);        // update cache first.
5636   if (m_aclFlags == kAclInvalid)  // -1 means invalid value, so get it from db.
5637   {
5638     nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
5639     bool dbWasOpen = (mDatabase != nullptr);
5640     rv = GetDatabase();
5641 
5642     if (mDatabase) {
5643       rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
5644       if (NS_SUCCEEDED(rv) && dbFolderInfo) {
5645         rv = dbFolderInfo->GetUint32Property("aclFlags", 0, aclFlags);
5646         m_aclFlags = *aclFlags;
5647       }
5648       // if getting the acl flags caused us to open the db, release the ref
5649       // because on startup, we might get acl on all folders,which will
5650       // leave a lot of db's open.
5651       if (!dbWasOpen) {
5652         mDatabase->Close(true /* commit changes */);
5653         mDatabase = nullptr;
5654       }
5655     }
5656   } else
5657     *aclFlags = m_aclFlags;
5658   return NS_OK;
5659 }
5660 
SetSupportedUserFlags(uint32_t userFlags)5661 nsresult nsImapMailFolder::SetSupportedUserFlags(uint32_t userFlags) {
5662   nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
5663   nsresult rv = GetDatabase();
5664 
5665   m_supportedUserFlags = userFlags;
5666   if (mDatabase) {
5667     rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
5668     if (NS_SUCCEEDED(rv) && dbFolderInfo)
5669       dbFolderInfo->SetUint32Property("imapFlags", userFlags);
5670   }
5671   return rv;
5672 }
5673 
GetSupportedUserFlags(uint32_t * userFlags)5674 nsresult nsImapMailFolder::GetSupportedUserFlags(uint32_t* userFlags) {
5675   NS_ENSURE_ARG_POINTER(userFlags);
5676 
5677   nsresult rv = NS_OK;
5678 
5679   ReadDBFolderInfo(false);        // update cache first.
5680   if (m_supportedUserFlags == 0)  // 0 means invalid value, so get it from db.
5681   {
5682     nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
5683     rv = GetDatabase();
5684 
5685     if (mDatabase) {
5686       rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
5687       if (NS_SUCCEEDED(rv) && dbFolderInfo) {
5688         rv = dbFolderInfo->GetUint32Property("imapFlags", 0, userFlags);
5689         m_supportedUserFlags = *userFlags;
5690       }
5691     }
5692   } else
5693     *userFlags = m_supportedUserFlags;
5694   return rv;
5695 }
5696 
GetCanOpenFolder(bool * aBool)5697 NS_IMETHODIMP nsImapMailFolder::GetCanOpenFolder(bool* aBool) {
5698   NS_ENSURE_ARG_POINTER(aBool);
5699   bool noSelect;
5700   GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
5701   *aBool = (noSelect) ? false : GetFolderACL()->GetCanIReadFolder();
5702   return NS_OK;
5703 }
5704 
5705 ///////// nsMsgIMAPFolderACL class ///////////////////////////////
5706 
5707 // This string is defined in the ACL RFC to be "anyone"
5708 #define IMAP_ACL_ANYONE_STRING "anyone"
5709 
nsMsgIMAPFolderACL(nsImapMailFolder * folder)5710 nsMsgIMAPFolderACL::nsMsgIMAPFolderACL(nsImapMailFolder* folder)
5711     : m_rightsHash(24) {
5712   NS_ASSERTION(folder, "need folder");
5713   m_folder = folder;
5714   m_aclCount = 0;
5715   BuildInitialACLFromCache();
5716 }
5717 
~nsMsgIMAPFolderACL()5718 nsMsgIMAPFolderACL::~nsMsgIMAPFolderACL() {}
5719 
5720 // We cache most of our own rights in the MSG_FOLDER_PREF_* flags
BuildInitialACLFromCache()5721 void nsMsgIMAPFolderACL::BuildInitialACLFromCache() {
5722   nsAutoCString myrights;
5723 
5724   uint32_t startingFlags;
5725   m_folder->GetAclFlags(&startingFlags);
5726 
5727   if (startingFlags & IMAP_ACL_READ_FLAG) myrights += "r";
5728   if (startingFlags & IMAP_ACL_STORE_SEEN_FLAG) myrights += "s";
5729   if (startingFlags & IMAP_ACL_WRITE_FLAG) myrights += "w";
5730   if (startingFlags & IMAP_ACL_INSERT_FLAG) myrights += "i";
5731   if (startingFlags & IMAP_ACL_POST_FLAG) myrights += "p";
5732   if (startingFlags & IMAP_ACL_CREATE_SUBFOLDER_FLAG) myrights += "c";
5733   if (startingFlags & IMAP_ACL_DELETE_FLAG) myrights += "dt";
5734   if (startingFlags & IMAP_ACL_ADMINISTER_FLAG) myrights += "a";
5735   if (startingFlags & IMAP_ACL_EXPUNGE_FLAG) myrights += "e";
5736 
5737   if (!myrights.IsEmpty()) SetFolderRightsForUser(EmptyCString(), myrights);
5738 }
5739 
UpdateACLCache()5740 void nsMsgIMAPFolderACL::UpdateACLCache() {
5741   uint32_t startingFlags = 0;
5742   m_folder->GetAclFlags(&startingFlags);
5743 
5744   if (GetCanIReadFolder())
5745     startingFlags |= IMAP_ACL_READ_FLAG;
5746   else
5747     startingFlags &= ~IMAP_ACL_READ_FLAG;
5748 
5749   if (GetCanIStoreSeenInFolder())
5750     startingFlags |= IMAP_ACL_STORE_SEEN_FLAG;
5751   else
5752     startingFlags &= ~IMAP_ACL_STORE_SEEN_FLAG;
5753 
5754   if (GetCanIWriteFolder())
5755     startingFlags |= IMAP_ACL_WRITE_FLAG;
5756   else
5757     startingFlags &= ~IMAP_ACL_WRITE_FLAG;
5758 
5759   if (GetCanIInsertInFolder())
5760     startingFlags |= IMAP_ACL_INSERT_FLAG;
5761   else
5762     startingFlags &= ~IMAP_ACL_INSERT_FLAG;
5763 
5764   if (GetCanIPostToFolder())
5765     startingFlags |= IMAP_ACL_POST_FLAG;
5766   else
5767     startingFlags &= ~IMAP_ACL_POST_FLAG;
5768 
5769   if (GetCanICreateSubfolder())
5770     startingFlags |= IMAP_ACL_CREATE_SUBFOLDER_FLAG;
5771   else
5772     startingFlags &= ~IMAP_ACL_CREATE_SUBFOLDER_FLAG;
5773 
5774   if (GetCanIDeleteInFolder())
5775     startingFlags |= IMAP_ACL_DELETE_FLAG;
5776   else
5777     startingFlags &= ~IMAP_ACL_DELETE_FLAG;
5778 
5779   if (GetCanIAdministerFolder())
5780     startingFlags |= IMAP_ACL_ADMINISTER_FLAG;
5781   else
5782     startingFlags &= ~IMAP_ACL_ADMINISTER_FLAG;
5783 
5784   if (GetCanIExpungeFolder())
5785     startingFlags |= IMAP_ACL_EXPUNGE_FLAG;
5786   else
5787     startingFlags &= ~IMAP_ACL_EXPUNGE_FLAG;
5788 
5789   m_folder->SetAclFlags(startingFlags);
5790 }
5791 
SetFolderRightsForUser(const nsACString & userName,const nsACString & rights)5792 bool nsMsgIMAPFolderACL::SetFolderRightsForUser(const nsACString& userName,
5793                                                 const nsACString& rights) {
5794   nsCString myUserName;
5795   nsCOMPtr<nsIMsgIncomingServer> server;
5796   nsresult rv = m_folder->GetServer(getter_AddRefs(server));
5797   NS_ENSURE_SUCCESS(rv, false);
5798 
5799   // we need the real user name to match with what the imap server returns
5800   // in the acl response.
5801   server->GetRealUsername(myUserName);
5802 
5803   nsAutoCString ourUserName;
5804   if (userName.IsEmpty())
5805     ourUserName.Assign(myUserName);
5806   else
5807     ourUserName.Assign(userName);
5808 
5809   if (ourUserName.IsEmpty()) return false;
5810 
5811   ToLowerCase(ourUserName);
5812   nsCString oldValue = m_rightsHash.Get(ourUserName);
5813   if (!oldValue.IsEmpty()) {
5814     m_rightsHash.Remove(ourUserName);
5815     m_aclCount--;
5816     NS_ASSERTION(m_aclCount >= 0, "acl count can't go negative");
5817   }
5818   m_aclCount++;
5819   m_rightsHash.InsertOrUpdate(ourUserName, PromiseFlatCString(rights));
5820 
5821   if (myUserName.Equals(ourUserName) ||
5822       ourUserName.EqualsLiteral(IMAP_ACL_ANYONE_STRING))
5823     // if this is setting an ACL for me, cache it in the folder pref flags
5824     UpdateACLCache();
5825 
5826   return true;
5827 }
5828 
GetOtherUsersWithAccess(nsIUTF8StringEnumerator ** aResult)5829 NS_IMETHODIMP nsImapMailFolder::GetOtherUsersWithAccess(
5830     nsIUTF8StringEnumerator** aResult) {
5831   return GetFolderACL()->GetOtherUsers(aResult);
5832 }
5833 
GetOtherUsers(nsIUTF8StringEnumerator ** aResult)5834 nsresult nsMsgIMAPFolderACL::GetOtherUsers(nsIUTF8StringEnumerator** aResult) {
5835   nsCString myUserName;
5836   nsCOMPtr<nsIMsgIncomingServer> server;
5837   nsresult rv = m_folder->GetServer(getter_AddRefs(server));
5838   NS_ENSURE_SUCCESS(rv, rv);
5839   server->GetRealUsername(myUserName);
5840 
5841   // We need to filter out myUserName from m_rightsHash.
5842   nsTArray<nsCString>* resultArray = new nsTArray<nsCString>;
5843   for (auto iter = m_rightsHash.Iter(); !iter.Done(); iter.Next()) {
5844     if (!iter.Key().Equals(myUserName)) resultArray->AppendElement(iter.Key());
5845   }
5846 
5847   // enumerator will free resultArray
5848   return NS_NewAdoptingUTF8StringEnumerator(aResult, resultArray);
5849 }
5850 
GetPermissionsForUser(const nsACString & otherUser,nsACString & aResult)5851 nsresult nsImapMailFolder::GetPermissionsForUser(const nsACString& otherUser,
5852                                                  nsACString& aResult) {
5853   nsCString str;
5854   nsresult rv = GetFolderACL()->GetRightsStringForUser(otherUser, str);
5855   NS_ENSURE_SUCCESS(rv, rv);
5856   aResult = str;
5857   return NS_OK;
5858 }
5859 
GetRightsStringForUser(const nsACString & inUserName,nsCString & rights)5860 nsresult nsMsgIMAPFolderACL::GetRightsStringForUser(
5861     const nsACString& inUserName, nsCString& rights) {
5862   nsCString userName;
5863   userName.Assign(inUserName);
5864   if (userName.IsEmpty()) {
5865     nsCOMPtr<nsIMsgIncomingServer> server;
5866 
5867     nsresult rv = m_folder->GetServer(getter_AddRefs(server));
5868     NS_ENSURE_SUCCESS(rv, rv);
5869     // we need the real user name to match with what the imap server returns
5870     // in the acl response.
5871     server->GetRealUsername(userName);
5872   }
5873   ToLowerCase(userName);
5874   rights = m_rightsHash.Get(userName);
5875   return NS_OK;
5876 }
5877 
5878 // First looks for individual user;  then looks for 'anyone' if the user isn't
5879 // found. Returns defaultIfNotFound, if neither are found.
GetFlagSetInRightsForUser(const nsACString & userName,char flag,bool defaultIfNotFound)5880 bool nsMsgIMAPFolderACL::GetFlagSetInRightsForUser(const nsACString& userName,
5881                                                    char flag,
5882                                                    bool defaultIfNotFound) {
5883   nsCString flags;
5884   nsresult rv = GetRightsStringForUser(userName, flags);
5885   NS_ENSURE_SUCCESS(rv, defaultIfNotFound);
5886   if (flags.IsEmpty()) {
5887     nsCString anyoneFlags;
5888     GetRightsStringForUser(nsLiteralCString(IMAP_ACL_ANYONE_STRING),
5889                            anyoneFlags);
5890     if (anyoneFlags.IsEmpty()) return defaultIfNotFound;
5891     return (anyoneFlags.FindChar(flag) != kNotFound);
5892   }
5893   return (flags.FindChar(flag) != kNotFound);
5894 }
5895 
GetCanUserLookupFolder(const nsACString & userName)5896 bool nsMsgIMAPFolderACL::GetCanUserLookupFolder(const nsACString& userName) {
5897   return GetFlagSetInRightsForUser(userName, 'l', false);
5898 }
5899 
GetCanUserReadFolder(const nsACString & userName)5900 bool nsMsgIMAPFolderACL::GetCanUserReadFolder(const nsACString& userName) {
5901   return GetFlagSetInRightsForUser(userName, 'r', false);
5902 }
5903 
GetCanUserStoreSeenInFolder(const nsACString & userName)5904 bool nsMsgIMAPFolderACL::GetCanUserStoreSeenInFolder(
5905     const nsACString& userName) {
5906   return GetFlagSetInRightsForUser(userName, 's', false);
5907 }
5908 
GetCanUserWriteFolder(const nsACString & userName)5909 bool nsMsgIMAPFolderACL::GetCanUserWriteFolder(const nsACString& userName) {
5910   return GetFlagSetInRightsForUser(userName, 'w', false);
5911 }
5912 
GetCanUserInsertInFolder(const nsACString & userName)5913 bool nsMsgIMAPFolderACL::GetCanUserInsertInFolder(const nsACString& userName) {
5914   return GetFlagSetInRightsForUser(userName, 'i', false);
5915 }
5916 
GetCanUserPostToFolder(const nsACString & userName)5917 bool nsMsgIMAPFolderACL::GetCanUserPostToFolder(const nsACString& userName) {
5918   return GetFlagSetInRightsForUser(userName, 'p', false);
5919 }
5920 
GetCanUserCreateSubfolder(const nsACString & userName)5921 bool nsMsgIMAPFolderACL::GetCanUserCreateSubfolder(const nsACString& userName) {
5922   return GetFlagSetInRightsForUser(userName, 'c', false);
5923 }
5924 
GetCanUserDeleteInFolder(const nsACString & userName)5925 bool nsMsgIMAPFolderACL::GetCanUserDeleteInFolder(const nsACString& userName) {
5926   return GetFlagSetInRightsForUser(userName, 'd', false) ||
5927          GetFlagSetInRightsForUser(userName, 't', false);
5928 }
5929 
GetCanUserAdministerFolder(const nsACString & userName)5930 bool nsMsgIMAPFolderACL::GetCanUserAdministerFolder(
5931     const nsACString& userName) {
5932   return GetFlagSetInRightsForUser(userName, 'a', false);
5933 }
5934 
GetCanILookupFolder()5935 bool nsMsgIMAPFolderACL::GetCanILookupFolder() {
5936   return GetFlagSetInRightsForUser(EmptyCString(), 'l', true);
5937 }
5938 
GetCanIReadFolder()5939 bool nsMsgIMAPFolderACL::GetCanIReadFolder() {
5940   return GetFlagSetInRightsForUser(EmptyCString(), 'r', true);
5941 }
5942 
GetCanIStoreSeenInFolder()5943 bool nsMsgIMAPFolderACL::GetCanIStoreSeenInFolder() {
5944   return GetFlagSetInRightsForUser(EmptyCString(), 's', true);
5945 }
5946 
GetCanIWriteFolder()5947 bool nsMsgIMAPFolderACL::GetCanIWriteFolder() {
5948   return GetFlagSetInRightsForUser(EmptyCString(), 'w', true);
5949 }
5950 
GetCanIInsertInFolder()5951 bool nsMsgIMAPFolderACL::GetCanIInsertInFolder() {
5952   return GetFlagSetInRightsForUser(EmptyCString(), 'i', true);
5953 }
5954 
GetCanIPostToFolder()5955 bool nsMsgIMAPFolderACL::GetCanIPostToFolder() {
5956   return GetFlagSetInRightsForUser(EmptyCString(), 'p', true);
5957 }
5958 
GetCanICreateSubfolder()5959 bool nsMsgIMAPFolderACL::GetCanICreateSubfolder() {
5960   return GetFlagSetInRightsForUser(EmptyCString(), 'c', true);
5961 }
5962 
GetCanIDeleteInFolder()5963 bool nsMsgIMAPFolderACL::GetCanIDeleteInFolder() {
5964   return GetFlagSetInRightsForUser(EmptyCString(), 'd', true) ||
5965          GetFlagSetInRightsForUser(EmptyCString(), 't', true);
5966 }
5967 
GetCanIAdministerFolder()5968 bool nsMsgIMAPFolderACL::GetCanIAdministerFolder() {
5969   return GetFlagSetInRightsForUser(EmptyCString(), 'a', true);
5970 }
5971 
GetCanIExpungeFolder()5972 bool nsMsgIMAPFolderACL::GetCanIExpungeFolder() {
5973   return GetFlagSetInRightsForUser(EmptyCString(), 'e', true) ||
5974          GetFlagSetInRightsForUser(EmptyCString(), 'd', true);
5975 }
5976 
5977 // We use this to see if the ACLs think a folder is shared or not.
5978 // We will define "Shared" in 5.0 to mean:
5979 // At least one user other than the currently authenticated user has at least
5980 // one explicitly-listed ACL right on that folder.
GetIsFolderShared()5981 bool nsMsgIMAPFolderACL::GetIsFolderShared() {
5982   // If we have more than one ACL count for this folder, which means that
5983   // someone other than ourself has rights on it, then it is "shared."
5984   if (m_aclCount > 1) return true;
5985 
5986   // Or, if "anyone" has rights to it, it is shared.
5987   nsCString anyonesRights =
5988       m_rightsHash.Get(nsLiteralCString(IMAP_ACL_ANYONE_STRING));
5989   return (!anyonesRights.IsEmpty());
5990 }
5991 
GetDoIHaveFullRightsForFolder()5992 bool nsMsgIMAPFolderACL::GetDoIHaveFullRightsForFolder() {
5993   return (GetCanIReadFolder() && GetCanIWriteFolder() &&
5994           GetCanIInsertInFolder() && GetCanIAdministerFolder() &&
5995           GetCanICreateSubfolder() && GetCanIDeleteInFolder() &&
5996           GetCanILookupFolder() && GetCanIStoreSeenInFolder() &&
5997           GetCanIExpungeFolder() && GetCanIPostToFolder());
5998 }
5999 
6000 // Returns a newly allocated string describing these rights
CreateACLRightsString(nsAString & aRightsString)6001 nsresult nsMsgIMAPFolderACL::CreateACLRightsString(nsAString& aRightsString) {
6002   nsString curRight;
6003   nsCOMPtr<nsIStringBundle> bundle;
6004   nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
6005   NS_ENSURE_SUCCESS(rv, rv);
6006 
6007   if (GetDoIHaveFullRightsForFolder()) {
6008     nsAutoString result;
6009     rv = bundle->GetStringFromName("imapAclFullRights", result);
6010     aRightsString.Assign(result);
6011     return rv;
6012   }
6013 
6014   if (GetCanIReadFolder()) {
6015     bundle->GetStringFromName("imapAclReadRight", curRight);
6016     aRightsString.Append(curRight);
6017   }
6018   if (GetCanIWriteFolder()) {
6019     if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
6020     bundle->GetStringFromName("imapAclWriteRight", curRight);
6021     aRightsString.Append(curRight);
6022   }
6023   if (GetCanIInsertInFolder()) {
6024     if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
6025     bundle->GetStringFromName("imapAclInsertRight", curRight);
6026     aRightsString.Append(curRight);
6027   }
6028   if (GetCanILookupFolder()) {
6029     if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
6030     bundle->GetStringFromName("imapAclLookupRight", curRight);
6031     aRightsString.Append(curRight);
6032   }
6033   if (GetCanIStoreSeenInFolder()) {
6034     if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
6035     bundle->GetStringFromName("imapAclSeenRight", curRight);
6036     aRightsString.Append(curRight);
6037   }
6038   if (GetCanIDeleteInFolder()) {
6039     if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
6040     bundle->GetStringFromName("imapAclDeleteRight", curRight);
6041     aRightsString.Append(curRight);
6042   }
6043   if (GetCanIExpungeFolder()) {
6044     if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
6045     bundle->GetStringFromName("imapAclExpungeRight", curRight);
6046     aRightsString.Append(curRight);
6047   }
6048   if (GetCanICreateSubfolder()) {
6049     if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
6050     bundle->GetStringFromName("imapAclCreateRight", curRight);
6051     aRightsString.Append(curRight);
6052   }
6053   if (GetCanIPostToFolder()) {
6054     if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
6055     bundle->GetStringFromName("imapAclPostRight", curRight);
6056     aRightsString.Append(curRight);
6057   }
6058   if (GetCanIAdministerFolder()) {
6059     if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
6060     bundle->GetStringFromName("imapAclAdministerRight", curRight);
6061     aRightsString.Append(curRight);
6062   }
6063   return rv;
6064 }
6065 
GetFilePath(nsIFile ** aPathName)6066 NS_IMETHODIMP nsImapMailFolder::GetFilePath(nsIFile** aPathName) {
6067   // this will return a copy of mPath, which is what we want.
6068   // this will also initialize mPath using parseURI if it isn't already done
6069   return nsMsgDBFolder::GetFilePath(aPathName);
6070 }
6071 
SetFilePath(nsIFile * aPathName)6072 NS_IMETHODIMP nsImapMailFolder::SetFilePath(nsIFile* aPathName) {
6073   return nsMsgDBFolder::SetFilePath(
6074       aPathName);  // call base class so mPath will get set
6075 }
6076 
DisplayStatusMsg(nsIImapUrl * aImapUrl,const nsAString & msg)6077 nsresult nsImapMailFolder::DisplayStatusMsg(nsIImapUrl* aImapUrl,
6078                                             const nsAString& msg) {
6079   nsCOMPtr<nsIImapMockChannel> mockChannel;
6080   aImapUrl->GetMockChannel(getter_AddRefs(mockChannel));
6081   if (mockChannel) {
6082     nsCOMPtr<nsIProgressEventSink> progressSink;
6083     mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
6084     if (progressSink) {
6085       progressSink->OnStatus(mockChannel, NS_OK,
6086                              PromiseFlatString(msg).get());  // XXX i18n message
6087     }
6088   }
6089   return NS_OK;
6090 }
6091 
6092 NS_IMETHODIMP
ProgressStatusString(nsIImapProtocol * aProtocol,const char * aMsgName,const char16_t * extraInfo)6093 nsImapMailFolder::ProgressStatusString(nsIImapProtocol* aProtocol,
6094                                        const char* aMsgName,
6095                                        const char16_t* extraInfo) {
6096   nsString progressMsg;
6097 
6098   nsCOMPtr<nsIMsgIncomingServer> server;
6099   nsresult rv = GetServer(getter_AddRefs(server));
6100   if (NS_SUCCEEDED(rv) && server) {
6101     nsCOMPtr<nsIImapServerSink> serverSink = do_QueryInterface(server);
6102     if (serverSink) serverSink->GetImapStringByName(aMsgName, progressMsg);
6103   }
6104   if (progressMsg.IsEmpty())
6105     IMAPGetStringByName(aMsgName, getter_Copies(progressMsg));
6106 
6107   if (aProtocol && !progressMsg.IsEmpty()) {
6108     nsCOMPtr<nsIImapUrl> imapUrl;
6109     aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
6110     if (imapUrl) {
6111       if (extraInfo) {
6112         nsString printfString;
6113         nsTextFormatter::ssprintf(printfString, progressMsg.get(), extraInfo);
6114         progressMsg = printfString;
6115       }
6116 
6117       DisplayStatusMsg(imapUrl, progressMsg);
6118     }
6119   }
6120   return NS_OK;
6121 }
6122 
6123 NS_IMETHODIMP
PercentProgress(nsIImapProtocol * aProtocol,nsACString const & aFmtStringName,nsAString const & aMailboxName,int64_t aCurrentProgress,int64_t aMaxProgress)6124 nsImapMailFolder::PercentProgress(nsIImapProtocol* aProtocol,
6125                                   nsACString const& aFmtStringName,
6126                                   nsAString const& aMailboxName,
6127                                   int64_t aCurrentProgress,
6128                                   int64_t aMaxProgress) {
6129   if (aProtocol) {
6130     nsCOMPtr<nsIImapUrl> imapUrl;
6131     aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
6132     if (imapUrl) {
6133       nsCOMPtr<nsIImapMockChannel> mockChannel;
6134       imapUrl->GetMockChannel(getter_AddRefs(mockChannel));
6135       if (mockChannel) {
6136         nsCOMPtr<nsIProgressEventSink> progressSink;
6137         mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
6138         if (progressSink) {
6139           progressSink->OnProgress(mockChannel, aCurrentProgress, aMaxProgress);
6140 
6141           if (!aFmtStringName.IsEmpty()) {
6142             // There's a progress message to format (the progress messages are
6143             // all localized and expect three params).
6144             nsAutoString current;
6145             current.AppendInt(aCurrentProgress);
6146             nsAutoString expected;
6147             expected.AppendInt(aMaxProgress);
6148             nsAutoString mailbox(aMailboxName);
6149             AutoTArray<nsString, 3> params = {current, expected, mailbox};
6150 
6151             nsCOMPtr<nsIStringBundle> bundle;
6152             nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
6153             NS_ENSURE_SUCCESS(rv, rv);
6154 
6155             nsString progressText;
6156             rv = bundle->FormatStringFromName(
6157                 PromiseFlatCString(aFmtStringName).get(), params, progressText);
6158             NS_ENSURE_SUCCESS(rv, rv);
6159             if (!progressText.IsEmpty()) {
6160               progressSink->OnStatus(mockChannel, NS_OK, progressText.get());
6161             }
6162           }
6163         }
6164       }
6165     }
6166   }
6167   return NS_OK;
6168 }
6169 
6170 NS_IMETHODIMP
CopyNextStreamMessage(bool copySucceeded,nsISupports * copyState)6171 nsImapMailFolder::CopyNextStreamMessage(bool copySucceeded,
6172                                         nsISupports* copyState) {
6173   // if copy has failed it could be either user interrupted it or for some other
6174   // reason don't do any subsequent copies or delete src messages if it is move
6175   if (!copySucceeded) return NS_OK;
6176   nsresult rv;
6177   nsCOMPtr<nsImapMailCopyState> mailCopyState =
6178       do_QueryInterface(copyState, &rv);
6179   if (NS_FAILED(rv)) {
6180     MOZ_LOG(IMAP, mozilla::LogLevel::Info,
6181             ("QI copyState failed: %" PRIx32, static_cast<uint32_t>(rv)));
6182     return rv;  // this can fail...
6183   }
6184 
6185   if (!mailCopyState->m_streamCopy) return NS_OK;
6186 
6187   if (mailCopyState->m_curIndex < mailCopyState->m_messages.Length()) {
6188     MOZ_LOG(
6189         IMAP, mozilla::LogLevel::Info,
6190         ("CopyNextStreamMessage: Copying %u of %u", mailCopyState->m_curIndex,
6191          (uint32_t)mailCopyState->m_messages.Length()));
6192     nsIMsgDBHdr* message = mailCopyState->m_messages[mailCopyState->m_curIndex];
6193     bool isRead;
6194     message->GetIsRead(&isRead);
6195     mailCopyState->m_unreadCount = (isRead) ? 0 : 1;
6196     rv = CopyStreamMessage(message, this, mailCopyState->m_msgWindow,
6197                            mailCopyState->m_isMove);
6198   } else {
6199     // Notify of move/copy completion in case we have some source headers
6200     nsCOMPtr<nsIMsgFolderNotificationService> notifier(
6201         do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
6202     if (notifier && !mailCopyState->m_messages.IsEmpty()) {
6203       notifier->NotifyMsgsMoveCopyCompleted(
6204           mailCopyState->m_isMove, mailCopyState->m_messages, this, {});
6205     }
6206     if (mailCopyState->m_isMove) {
6207       nsCOMPtr<nsIMsgFolder> srcFolder(
6208           do_QueryInterface(mailCopyState->m_srcSupport, &rv));
6209       if (NS_SUCCEEDED(rv) && srcFolder) {
6210         srcFolder->DeleteMessages(mailCopyState->m_messages, nullptr, true,
6211                                   true, nullptr, false);
6212         // we want to send this notification after the source messages have
6213         // been deleted.
6214         nsCOMPtr<nsIMsgLocalMailFolder> popFolder(do_QueryInterface(srcFolder));
6215         if (popFolder)  // needed if move pop->imap to notify FE
6216           srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
6217       }
6218     }
6219   }
6220   if (NS_FAILED(rv)) (void)OnCopyCompleted(mailCopyState->m_srcSupport, rv);
6221 
6222   return rv;
6223 }
6224 
6225 NS_IMETHODIMP
SetUrlState(nsIImapProtocol * aProtocol,nsIMsgMailNewsUrl * aUrl,bool isRunning,bool aSuspend,nsresult statusCode)6226 nsImapMailFolder::SetUrlState(nsIImapProtocol* aProtocol,
6227                               nsIMsgMailNewsUrl* aUrl, bool isRunning,
6228                               bool aSuspend, nsresult statusCode) {
6229   // If we have no path, then the folder has been shutdown, and there's
6230   // no point in doing anything...
6231   if (!mPath) return NS_OK;
6232   if (!isRunning) {
6233     ProgressStatusString(aProtocol, "imapDone", nullptr);
6234     m_urlRunning = false;
6235     // if no protocol, then we're reading from the mem or disk cache
6236     // and we don't want to end the offline download just yet.
6237     if (aProtocol) {
6238       EndOfflineDownload();
6239       m_downloadingFolderForOfflineUse = false;
6240     }
6241     nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
6242     if (imapUrl) {
6243       nsImapAction imapAction;
6244       imapUrl->GetImapAction(&imapAction);
6245       // if the server doesn't support copyUID, then SetCopyResponseUid won't
6246       // get called, so we need to clear m_pendingOfflineMoves when the online
6247       // move operation has finished.
6248       if (imapAction == nsIImapUrl::nsImapOnlineMove)
6249         m_pendingOfflineMoves.Clear();
6250     }
6251   }
6252   if (aUrl && !aSuspend) return aUrl->SetUrlState(isRunning, statusCode);
6253   return statusCode;
6254 }
6255 
6256 // used when copying from local mail folder, or other imap server)
CopyMessagesWithStream(nsIMsgFolder * srcFolder,nsTArray<RefPtr<nsIMsgDBHdr>> const & messages,bool isMove,bool isCrossServerOp,nsIMsgWindow * msgWindow,nsIMsgCopyServiceListener * listener,bool allowUndo)6257 nsresult nsImapMailFolder::CopyMessagesWithStream(
6258     nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
6259     bool isMove, bool isCrossServerOp, nsIMsgWindow* msgWindow,
6260     nsIMsgCopyServiceListener* listener, bool allowUndo) {
6261   NS_ENSURE_ARG_POINTER(srcFolder);
6262   nsresult rv;
6263   nsCOMPtr<nsISupports> aSupport(do_QueryInterface(srcFolder, &rv));
6264   NS_ENSURE_SUCCESS(rv, rv);
6265   rv = InitCopyState(aSupport, messages, isMove, false, isCrossServerOp, 0,
6266                      EmptyCString(), listener, msgWindow, allowUndo);
6267   if (NS_FAILED(rv)) return rv;
6268 
6269   m_copyState->m_streamCopy = true;
6270 
6271   // ** jt - needs to create server to server move/copy undo msg txn
6272   if (m_copyState->m_allowUndo) {
6273     nsAutoCString messageIds;
6274     nsTArray<nsMsgKey> srcKeyArray;
6275     rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
6276 
6277     RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
6278 
6279     if (!undoMsgTxn ||
6280         NS_FAILED(undoMsgTxn->Init(srcFolder, &srcKeyArray, messageIds.get(),
6281                                    this, true, isMove)))
6282       return NS_ERROR_OUT_OF_MEMORY;
6283 
6284     if (isMove) {
6285       if (mFlags & nsMsgFolderFlags::Trash)
6286         undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
6287       else
6288         undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
6289     } else
6290       undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
6291     m_copyState->m_undoMsgTxn = undoMsgTxn;
6292   }
6293   if (NS_SUCCEEDED(rv)) CopyStreamMessage(messages[0], this, msgWindow, isMove);
6294   return rv;  // we are clearing copy state in CopyMessages on failure
6295 }
6296 
GetClearedOriginalOp(nsIMsgOfflineImapOperation * op,nsIMsgOfflineImapOperation ** originalOp,nsIMsgDatabase ** originalDB)6297 nsresult nsImapMailFolder::GetClearedOriginalOp(
6298     nsIMsgOfflineImapOperation* op, nsIMsgOfflineImapOperation** originalOp,
6299     nsIMsgDatabase** originalDB) {
6300   nsCOMPtr<nsIMsgOfflineImapOperation> returnOp;
6301   nsOfflineImapOperationType opType;
6302   op->GetOperation(&opType);
6303   NS_ASSERTION(opType & nsIMsgOfflineImapOperation::kMoveResult,
6304                "not an offline move op");
6305 
6306   nsCString sourceFolderURI;
6307   op->GetSourceFolderURI(sourceFolderURI);
6308 
6309   nsresult rv;
6310   nsCOMPtr<nsIMsgFolder> sourceFolder;
6311   rv = GetOrCreateFolder(sourceFolderURI, getter_AddRefs(sourceFolder));
6312   NS_ENSURE_SUCCESS(rv, rv);
6313 
6314   nsCOMPtr<nsIDBFolderInfo> folderInfo;
6315   sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
6316   if (*originalDB) {
6317     nsMsgKey originalKey;
6318     op->GetMessageKey(&originalKey);
6319     rv = (*originalDB)
6320              ->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
6321     if (NS_SUCCEEDED(rv) && returnOp) {
6322       nsCString moveDestination;
6323       nsCString thisFolderURI;
6324       GetURI(thisFolderURI);
6325       returnOp->GetDestinationFolderURI(moveDestination);
6326       if (moveDestination.Equals(thisFolderURI))
6327         returnOp->ClearOperation(nsIMsgOfflineImapOperation::kMoveResult);
6328     }
6329   }
6330   returnOp.forget(originalOp);
6331   return rv;
6332 }
6333 
GetOriginalOp(nsIMsgOfflineImapOperation * op,nsIMsgOfflineImapOperation ** originalOp,nsIMsgDatabase ** originalDB)6334 nsresult nsImapMailFolder::GetOriginalOp(
6335     nsIMsgOfflineImapOperation* op, nsIMsgOfflineImapOperation** originalOp,
6336     nsIMsgDatabase** originalDB) {
6337   nsCOMPtr<nsIMsgOfflineImapOperation> returnOp;
6338   nsCString sourceFolderURI;
6339   op->GetSourceFolderURI(sourceFolderURI);
6340 
6341   nsresult rv;
6342   nsCOMPtr<nsIMsgFolder> sourceFolder;
6343   rv = GetOrCreateFolder(sourceFolderURI, getter_AddRefs(sourceFolder));
6344   NS_ENSURE_SUCCESS(rv, rv);
6345   nsCOMPtr<nsIDBFolderInfo> folderInfo;
6346   sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
6347   if (*originalDB) {
6348     nsMsgKey originalKey;
6349     op->GetMessageKey(&originalKey);
6350     rv = (*originalDB)
6351              ->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
6352   }
6353   returnOp.forget(originalOp);
6354   return rv;
6355 }
6356 
CopyOfflineMsgBody(nsIMsgFolder * srcFolder,nsIMsgDBHdr * destHdr,nsIMsgDBHdr * origHdr,nsIInputStream * inputStream,nsIOutputStream * outputStream)6357 nsresult nsImapMailFolder::CopyOfflineMsgBody(nsIMsgFolder* srcFolder,
6358                                               nsIMsgDBHdr* destHdr,
6359                                               nsIMsgDBHdr* origHdr,
6360                                               nsIInputStream* inputStream,
6361                                               nsIOutputStream* outputStream) {
6362   nsresult rv;
6363   nsCOMPtr<nsISeekableStream> seekable(do_QueryInterface(outputStream, &rv));
6364   NS_ENSURE_SUCCESS(rv, rv);
6365   uint64_t messageOffset;
6366   uint32_t messageSize;
6367   origHdr->GetMessageOffset(&messageOffset);
6368   if (!messageOffset) {
6369     // Some offline stores may contain a bug where the storeToken is set but
6370     // the messageOffset is zero. Detect cases like this, and use storeToken
6371     // to set the missing messageOffset. Note this assumes mbox.
6372     nsCOMPtr<nsIMsgPluggableStore> offlineStore;
6373     (void)GetMsgStore(getter_AddRefs(offlineStore));
6374     if (offlineStore) {
6375       nsAutoCString type;
6376       offlineStore->GetStoreType(type);
6377       if (type.EqualsLiteral("mbox")) {
6378         nsCString storeToken;
6379         origHdr->GetStringProperty("storeToken", getter_Copies(storeToken));
6380         if (!storeToken.IsEmpty())
6381           messageOffset = ParseUint64Str(storeToken.get());
6382       }
6383     }
6384   }
6385   origHdr->GetOfflineMessageSize(&messageSize);
6386   if (!messageSize) {
6387     nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(srcFolder);
6388     if (localFolder)  // can just use regular message size
6389       origHdr->GetMessageSize(&messageSize);
6390   }
6391   int64_t tellPos;
6392   seekable->Tell(&tellPos);
6393   destHdr->SetMessageOffset(tellPos);
6394   nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(inputStream);
6395   NS_ASSERTION(seekStream, "non seekable stream - can't read from offline msg");
6396   if (seekStream) {
6397     rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, messageOffset);
6398     if (NS_SUCCEEDED(rv)) {
6399       // now, copy the dest folder offline store msg to the temp file
6400       char* inputBuffer = (char*)PR_Malloc(FILE_IO_BUFFER_SIZE);
6401       int32_t bytesLeft;
6402       uint32_t bytesRead, bytesWritten;
6403       bytesLeft = messageSize;
6404       rv = (inputBuffer) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
6405       while (bytesLeft > 0 && NS_SUCCEEDED(rv)) {
6406         rv = inputStream->Read(inputBuffer, FILE_IO_BUFFER_SIZE, &bytesRead);
6407         if (NS_SUCCEEDED(rv) && bytesRead > 0) {
6408           rv = outputStream->Write(inputBuffer,
6409                                    std::min((int32_t)bytesRead, bytesLeft),
6410                                    &bytesWritten);
6411           NS_ASSERTION(
6412               (int32_t)bytesWritten == std::min((int32_t)bytesRead, bytesLeft),
6413               "wrote out incorrect number of bytes");
6414         } else
6415           break;
6416         bytesLeft -= bytesRead;
6417       }
6418       PR_FREEIF(inputBuffer);
6419     }
6420   }
6421   if (NS_SUCCEEDED(rv)) {
6422     outputStream->Flush();
6423     uint32_t resultFlags;
6424     destHdr->OrFlags(nsMsgMessageFlags::Offline, &resultFlags);
6425     destHdr->SetOfflineMessageSize(messageSize);
6426   }
6427   return rv;
6428 }
6429 
FindOpenRange(nsMsgKey & fakeBase,uint32_t srcCount)6430 nsresult nsImapMailFolder::FindOpenRange(nsMsgKey& fakeBase,
6431                                          uint32_t srcCount) {
6432   nsresult rv = GetDatabase();
6433   NS_ENSURE_SUCCESS(rv, rv);
6434 
6435   nsMsgKey newBase = fakeBase - 1;
6436   uint32_t freeCount = 0;
6437   while (freeCount != srcCount && newBase > 0) {
6438     bool containsKey;
6439     if (NS_SUCCEEDED(mDatabase->ContainsKey(newBase, &containsKey)) &&
6440         !containsKey)
6441       freeCount++;
6442     else
6443       freeCount = 0;
6444     newBase--;
6445   }
6446   if (!newBase) return NS_ERROR_FAILURE;
6447   fakeBase = newBase;
6448   return NS_OK;
6449 }
6450 
6451 // this imap folder is the destination of an offline move/copy.
6452 // We are either offline, or doing a pseudo-offline delete (where we do an
6453 // offline delete, load the next message, then playback the offline delete).
CopyMessagesOffline(nsIMsgFolder * srcFolder,nsTArray<RefPtr<nsIMsgDBHdr>> const & messages,bool isMove,nsIMsgWindow * msgWindow,nsIMsgCopyServiceListener * listener)6454 nsresult nsImapMailFolder::CopyMessagesOffline(
6455     nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
6456     bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) {
6457   nsresult rv;
6458   nsresult stopit = NS_OK;
6459   nsCOMPtr<nsIMsgDatabase> sourceMailDB;
6460   nsCOMPtr<nsIDBFolderInfo> srcDbFolderInfo;
6461   srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(srcDbFolderInfo),
6462                                   getter_AddRefs(sourceMailDB));
6463   bool deleteToTrash = false;
6464   bool deleteImmediately = false;
6465   uint32_t srcCount = messages.Length();
6466   nsCOMPtr<nsIImapIncomingServer> imapServer;
6467   rv = GetImapIncomingServer(getter_AddRefs(imapServer));
6468 
6469   nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrsCopied;
6470   nsTArray<RefPtr<nsIMsgDBHdr>> destMsgHdrs;
6471 
6472   if (NS_SUCCEEDED(rv) && imapServer) {
6473     nsMsgImapDeleteModel deleteModel;
6474     imapServer->GetDeleteModel(&deleteModel);
6475     deleteToTrash = (deleteModel == nsMsgImapDeleteModels::MoveToTrash);
6476     deleteImmediately = (deleteModel == nsMsgImapDeleteModels::DeleteNoTrash);
6477   }
6478 
6479   // This array is used only when we are actually removing the messages from the
6480   // source database.
6481   nsTArray<nsMsgKey> keysToDelete(
6482       (isMove && (deleteToTrash || deleteImmediately)) ? srcCount : 0);
6483 
6484   if (sourceMailDB) {
6485     // save the future ops in the source DB, if this is not a imap->local
6486     // copy/move
6487     nsCOMPtr<nsITransactionManager> txnMgr;
6488     if (msgWindow) msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
6489     if (txnMgr) txnMgr->BeginBatch(nullptr);
6490     nsCOMPtr<nsIMsgDatabase> database;
6491     GetMsgDatabase(getter_AddRefs(database));
6492     if (database) {
6493       // get the highest key in the dest db, so we can make up our fake keys
6494       nsMsgKey fakeBase = 1;
6495       nsCOMPtr<nsIDBFolderInfo> folderInfo;
6496       rv = database->GetDBFolderInfo(getter_AddRefs(folderInfo));
6497       NS_ENSURE_SUCCESS(rv, rv);
6498       nsMsgKey highWaterMark = nsMsgKey_None;
6499       folderInfo->GetHighWater(&highWaterMark);
6500       fakeBase += highWaterMark;
6501       nsMsgKey fakeTop = fakeBase + srcCount;
6502       // Check that we have enough room for the fake headers. If fakeTop
6503       // is <= highWaterMark, we've overflowed.
6504       if (fakeTop <= highWaterMark || fakeTop == nsMsgKey_None) {
6505         rv = FindOpenRange(fakeBase, srcCount);
6506         NS_ENSURE_SUCCESS(rv, rv);
6507       }
6508       // N.B. We must not return out of the for loop - we need the matching
6509       // end notifications to be sent.
6510       // We don't need to acquire the semaphor since this is synchronous
6511       // on the UI thread but we should check if the offline store is locked.
6512       bool isLocked;
6513       GetLocked(&isLocked);
6514       nsCOMPtr<nsIInputStream> inputStream;
6515       bool reusable = false;
6516       nsCOMPtr<nsIOutputStream> outputStream;
6517       nsTArray<nsMsgKey> addedKeys;
6518       nsTArray<nsMsgKey> srcKeyArray;
6519       nsCOMArray<nsIMsgDBHdr> addedHdrs;
6520       nsCOMArray<nsIMsgDBHdr> srcMsgs;
6521       nsOfflineImapOperationType moveCopyOpType;
6522       nsOfflineImapOperationType deleteOpType =
6523           nsIMsgOfflineImapOperation::kDeletedMsg;
6524       if (!deleteToTrash)
6525         deleteOpType = nsIMsgOfflineImapOperation::kMsgMarkedDeleted;
6526       nsCString messageIds;
6527       rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
6528       // put fake message in destination db, delete source if move
6529       EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false);
6530       for (uint32_t sourceKeyIndex = 0;
6531            NS_SUCCEEDED(stopit) && (sourceKeyIndex < srcCount);
6532            sourceKeyIndex++) {
6533         bool messageReturningHome = false;
6534         nsCString originalSrcFolderURI;
6535         srcFolder->GetURI(originalSrcFolderURI);
6536         RefPtr<nsIMsgDBHdr> message = messages[sourceKeyIndex];
6537         nsMsgKey originalKey;
6538         if (message)
6539           rv = message->GetMessageKey(&originalKey);
6540         else {
6541           NS_ERROR("bad msg in src array");
6542           continue;
6543         }
6544         nsCOMPtr<nsIMsgOfflineImapOperation> sourceOp;
6545         rv = sourceMailDB->GetOfflineOpForKey(originalKey, true,
6546                                               getter_AddRefs(sourceOp));
6547         if (NS_SUCCEEDED(rv) && sourceOp) {
6548           srcFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
6549           nsCOMPtr<nsIMsgDatabase> originalDB;
6550           nsOfflineImapOperationType opType;
6551           sourceOp->GetOperation(&opType);
6552           // if we already have an offline op for this key, then we need to see
6553           // if it was moved into the source folder while offline
6554           if (opType ==
6555               nsIMsgOfflineImapOperation::kMoveResult)  // offline move
6556           {
6557             // gracious me, we are moving something we already moved while
6558             // offline! find the original operation and clear it!
6559             nsCOMPtr<nsIMsgOfflineImapOperation> originalOp;
6560             rv = GetClearedOriginalOp(sourceOp, getter_AddRefs(originalOp),
6561                                       getter_AddRefs(originalDB));
6562             if (originalOp) {
6563               nsCString srcFolderURI;
6564               srcFolder->GetURI(srcFolderURI);
6565               sourceOp->GetSourceFolderURI(originalSrcFolderURI);
6566               sourceOp->GetMessageKey(&originalKey);
6567               if (isMove) sourceMailDB->RemoveOfflineOp(sourceOp);
6568               sourceOp = originalOp;
6569               if (originalSrcFolderURI.Equals(srcFolderURI)) {
6570                 messageReturningHome = true;
6571                 originalDB->RemoveOfflineOp(originalOp);
6572               }
6573             }
6574           }
6575           if (!messageReturningHome) {
6576             nsCString folderURI;
6577             GetURI(folderURI);
6578             if (isMove) {
6579               uint32_t msgSize;
6580               uint32_t msgFlags;
6581               imapMessageFlagsType newImapFlags = 0;
6582               message->GetMessageSize(&msgSize);
6583               message->GetFlags(&msgFlags);
6584               sourceOp->SetDestinationFolderURI(folderURI);  // offline move
6585               sourceOp->SetOperation(nsIMsgOfflineImapOperation::kMsgMoved);
6586               sourceOp->SetMsgSize(msgSize);
6587               newImapFlags = msgFlags & 0x7;
6588               if (msgFlags & nsMsgMessageFlags::Forwarded)
6589                 newImapFlags |= kImapMsgForwardedFlag;
6590               sourceOp->SetNewFlags(newImapFlags);
6591             } else
6592               sourceOp->AddMessageCopyOperation(folderURI);  // offline copy
6593 
6594             sourceOp->GetOperation(&moveCopyOpType);
6595             srcMsgs.AppendObject(message);
6596           }
6597           bool hasMsgOffline = false;
6598           srcFolder->HasMsgOffline(originalKey, &hasMsgOffline);
6599         } else
6600           stopit = NS_ERROR_FAILURE;
6601 
6602         nsCOMPtr<nsIMsgDBHdr> mailHdr;
6603         rv =
6604             sourceMailDB->GetMsgHdrForKey(originalKey, getter_AddRefs(mailHdr));
6605         if (NS_SUCCEEDED(rv) && mailHdr) {
6606           bool successfulCopy = false;
6607           nsMsgKey srcDBhighWaterMark;
6608           srcDbFolderInfo->GetHighWater(&srcDBhighWaterMark);
6609 
6610           nsCOMPtr<nsIMsgDBHdr> newMailHdr;
6611           rv = database->CopyHdrFromExistingHdr(fakeBase + sourceKeyIndex,
6612                                                 mailHdr, true,
6613                                                 getter_AddRefs(newMailHdr));
6614           if (!newMailHdr || NS_FAILED(rv)) {
6615             NS_ASSERTION(false, "failed to copy hdr");
6616             stopit = rv;
6617           }
6618 
6619           if (NS_SUCCEEDED(stopit)) {
6620             bool hasMsgOffline = false;
6621 
6622             destMsgHdrs.AppendElement(newMailHdr);
6623             srcFolder->HasMsgOffline(originalKey, &hasMsgOffline);
6624             newMailHdr->SetUint32Property("pseudoHdr", 1);
6625             if (!reusable)
6626               (void)srcFolder->GetMsgInputStream(newMailHdr, &reusable,
6627                                                  getter_AddRefs(inputStream));
6628 
6629             if (inputStream && hasMsgOffline && !isLocked) {
6630               rv = GetOfflineStoreOutputStream(newMailHdr,
6631                                                getter_AddRefs(outputStream));
6632               NS_ENSURE_SUCCESS(rv, rv);
6633 
6634               CopyOfflineMsgBody(srcFolder, newMailHdr, mailHdr, inputStream,
6635                                  outputStream);
6636               nsCOMPtr<nsIMsgPluggableStore> offlineStore;
6637               (void)GetMsgStore(getter_AddRefs(offlineStore));
6638               if (offlineStore)
6639                 offlineStore->FinishNewMessage(outputStream, newMailHdr);
6640             } else
6641               database->MarkOffline(fakeBase + sourceKeyIndex, false, nullptr);
6642 
6643             nsCOMPtr<nsIMsgOfflineImapOperation> destOp;
6644             database->GetOfflineOpForKey(fakeBase + sourceKeyIndex, true,
6645                                          getter_AddRefs(destOp));
6646             if (destOp) {
6647               // check if this is a move back to the original mailbox, in which
6648               // case we just delete the offline operation.
6649               if (messageReturningHome)
6650                 database->RemoveOfflineOp(destOp);
6651               else {
6652                 SetFlag(nsMsgFolderFlags::OfflineEvents);
6653                 destOp->SetSourceFolderURI(originalSrcFolderURI);
6654                 destOp->SetSrcMessageKey(originalKey);
6655                 addedKeys.AppendElement(fakeBase + sourceKeyIndex);
6656                 addedHdrs.AppendObject(newMailHdr);
6657               }
6658             } else
6659               stopit = NS_ERROR_FAILURE;
6660           }
6661           successfulCopy = NS_SUCCEEDED(stopit);
6662           nsMsgKey msgKey;
6663           mailHdr->GetMessageKey(&msgKey);
6664           if (isMove && successfulCopy) {
6665             if (deleteToTrash || deleteImmediately)
6666               keysToDelete.AppendElement(msgKey);
6667             else
6668               sourceMailDB->MarkImapDeleted(msgKey, true,
6669                                             nullptr);  // offline delete
6670           }
6671           if (successfulCopy)
6672             // This is for both moves and copies
6673             msgHdrsCopied.AppendElement(mailHdr);
6674         }
6675       }
6676       EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true);
6677       RefPtr<nsImapOfflineTxn> addHdrMsgTxn = new nsImapOfflineTxn(
6678           this, &addedKeys, nullptr, this, isMove,
6679           nsIMsgOfflineImapOperation::kAddedHeader, addedHdrs);
6680       if (addHdrMsgTxn && txnMgr) txnMgr->DoTransaction(addHdrMsgTxn);
6681       RefPtr<nsImapOfflineTxn> undoMsgTxn =
6682           new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this,
6683                                isMove, moveCopyOpType, srcMsgs);
6684       if (undoMsgTxn) {
6685         if (isMove) {
6686           undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
6687           nsCOMPtr<nsIMsgImapMailFolder> srcIsImap(
6688               do_QueryInterface(srcFolder));
6689           // remember this undo transaction so we can hook up the result
6690           // msg ids in the undo transaction.
6691           if (srcIsImap) {
6692             nsImapMailFolder* srcImapFolder =
6693                 static_cast<nsImapMailFolder*>(srcFolder);
6694             srcImapFolder->m_pendingOfflineMoves.AppendElement(undoMsgTxn);
6695           }
6696         } else
6697           undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
6698         // we're adding this undo action before the delete is successful. This
6699         // is evil, but 4.5 did it as well.
6700         if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
6701       }
6702       undoMsgTxn =
6703           new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this,
6704                                isMove, deleteOpType, srcMsgs);
6705       if (undoMsgTxn) {
6706         if (isMove) {
6707           if (mFlags & nsMsgFolderFlags::Trash)
6708             undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
6709           else
6710             undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
6711         } else
6712           undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
6713         if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
6714       }
6715       if (outputStream) outputStream->Close();
6716 
6717       if (isMove) sourceMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
6718       database->Commit(nsMsgDBCommitType::kLargeCommit);
6719       SummaryChanged();
6720       srcFolder->SummaryChanged();
6721     }
6722     if (txnMgr) txnMgr->EndBatch(false);
6723   }
6724 
6725   // Do this before delete, as it destroys the messages
6726   if (!msgHdrsCopied.IsEmpty()) {
6727     nsCOMPtr<nsIMsgFolderNotificationService> notifier(
6728         do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
6729     if (notifier) {
6730       notifier->NotifyMsgsMoveCopyCompleted(isMove, msgHdrsCopied, this,
6731                                             destMsgHdrs);
6732     }
6733   }
6734 
6735   if (isMove && NS_SUCCEEDED(rv) && (deleteToTrash || deleteImmediately)) {
6736     DeleteStoreMessages(keysToDelete, srcFolder);
6737     srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
6738                                    false);
6739     sourceMailDB->DeleteMessages(keysToDelete, nullptr);
6740     srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
6741                                    true);
6742   }
6743 
6744   nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
6745   OnCopyCompleted(srcSupport, rv);
6746 
6747   if (isMove)
6748     srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted
6749                                                   : kDeleteOrMoveMsgFailed);
6750   return rv;
6751 }
6752 
SetPendingAttributes(const nsTArray<RefPtr<nsIMsgDBHdr>> & messages,bool aIsMove,bool aSetOffline)6753 void nsImapMailFolder::SetPendingAttributes(
6754     const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, bool aIsMove,
6755     bool aSetOffline) {
6756   GetDatabase();
6757   if (!mDatabase) return;
6758 
6759   uint32_t supportedUserFlags;
6760   GetSupportedUserFlags(&supportedUserFlags);
6761 
6762   nsresult rv;
6763   nsCOMPtr<nsIPrefBranch> prefBranch(
6764       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
6765   NS_ENSURE_SUCCESS_VOID(rv);
6766 
6767   nsCString dontPreserve;
6768 
6769   // These preferences exist so that extensions can control which properties
6770   // are preserved in the database when a message is moved or copied. All
6771   // properties are preserved except those listed in these preferences
6772   if (aIsMove)
6773     prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove",
6774                             dontPreserve);
6775   else
6776     prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy",
6777                             dontPreserve);
6778 
6779   // We'll add spaces at beginning and end so we can search for space-name-space
6780   nsCString dontPreserveEx(" "_ns);
6781   dontPreserveEx.Append(dontPreserve);
6782   dontPreserveEx.Append(' ');
6783 
6784   // these properties are set as integers below, so don't set them again
6785   // in the iteration through the properties
6786   dontPreserveEx.AppendLiteral(
6787       "offlineMsgSize msgOffset flags priority pseudoHdr ");
6788 
6789   // these fields are either copied separately when the server does not support
6790   // custom IMAP flags, or managed directly through the flags
6791   dontPreserveEx.AppendLiteral("keywords label ");
6792 
6793   // check if any msg hdr has special flags or properties set
6794   // that we need to set on the dest hdr
6795   for (auto msgDBHdr : messages) {
6796     if (!(supportedUserFlags & kImapMsgSupportUserFlag)) {
6797       nsMsgLabelValue label;
6798       msgDBHdr->GetLabel(&label);
6799       if (label != 0) {
6800         nsAutoCString labelStr;
6801         labelStr.AppendInt(label);
6802         mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "label", labelStr.get());
6803       }
6804       nsCString keywords;
6805       msgDBHdr->GetStringProperty("keywords", getter_Copies(keywords));
6806       if (!keywords.IsEmpty())
6807         mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "keywords",
6808                                             keywords.get());
6809     }
6810 
6811     // do this even if the server supports user-defined flags.
6812     nsCOMPtr<nsIUTF8StringEnumerator> propertyEnumerator;
6813     nsresult rv =
6814         msgDBHdr->GetPropertyEnumerator(getter_AddRefs(propertyEnumerator));
6815     NS_ENSURE_SUCCESS_VOID(rv);
6816 
6817     nsAutoCString property;
6818     nsCString sourceString;
6819     bool hasMore;
6820     while (NS_SUCCEEDED(propertyEnumerator->HasMore(&hasMore)) && hasMore) {
6821       propertyEnumerator->GetNext(property);
6822       nsAutoCString propertyEx(" "_ns);
6823       propertyEx.Append(property);
6824       propertyEx.Append(' ');
6825       if (dontPreserveEx.Find(propertyEx) != kNotFound) continue;
6826 
6827       nsCString sourceString;
6828       msgDBHdr->GetStringProperty(property.get(), getter_Copies(sourceString));
6829       mDatabase->SetAttributeOnPendingHdr(msgDBHdr, property.get(),
6830                                           sourceString.get());
6831     }
6832 
6833     // Carry over HasRe flag.
6834     uint32_t flags;
6835     uint32_t storeFlags = 0;
6836     msgDBHdr->GetFlags(&flags);
6837     if (flags & nsMsgMessageFlags::HasRe) {
6838       storeFlags = nsMsgMessageFlags::HasRe;
6839       mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "flags", storeFlags);
6840     }
6841 
6842     uint32_t messageSize;
6843     uint64_t messageOffset;
6844     nsCString storeToken;
6845     msgDBHdr->GetMessageOffset(&messageOffset);
6846     msgDBHdr->GetOfflineMessageSize(&messageSize);
6847     msgDBHdr->GetStringProperty("storeToken", getter_Copies(storeToken));
6848     if (messageSize) {
6849       mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "offlineMsgSize",
6850                                                 messageSize);
6851       mDatabase->SetUint64AttributeOnPendingHdr(msgDBHdr, "msgOffset",
6852                                                 messageOffset);
6853       // Not always setting "flags" attribute to nsMsgMessageFlags::Offline
6854       // here because it can cause missing parts (inline or attachments)
6855       // when messages are moved or copied manually or by filter action.
6856       if (aSetOffline)
6857         mDatabase->SetUint32AttributeOnPendingHdr(
6858             msgDBHdr, "flags", storeFlags | nsMsgMessageFlags::Offline);
6859       mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "storeToken",
6860                                           storeToken.get());
6861     }
6862     nsMsgPriorityValue priority;
6863     msgDBHdr->GetPriority(&priority);
6864     if (priority != 0) {
6865       nsAutoCString priorityStr;
6866       priorityStr.AppendInt(priority);
6867       mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "priority",
6868                                           priorityStr.get());
6869     }
6870   }
6871 }
6872 
6873 NS_IMETHODIMP
CopyMessages(nsIMsgFolder * srcFolder,nsTArray<RefPtr<nsIMsgDBHdr>> const & messages,bool isMove,nsIMsgWindow * msgWindow,nsIMsgCopyServiceListener * listener,bool isFolder,bool allowUndo)6874 nsImapMailFolder::CopyMessages(
6875     nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
6876     bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener,
6877     bool isFolder,  // isFolder for future use when we do cross-server folder
6878                     // move/copy
6879     bool allowUndo) {
6880   UpdateTimestamps(allowUndo);
6881 
6882   nsresult rv;
6883   nsCOMPtr<nsIMsgIncomingServer> srcServer;
6884   nsCOMPtr<nsIMsgIncomingServer> dstServer;
6885   nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
6886   bool sameServer = false;
6887 
6888   rv = srcFolder->GetServer(getter_AddRefs(srcServer));
6889   if (NS_FAILED(rv)) goto done;
6890 
6891   rv = GetServer(getter_AddRefs(dstServer));
6892   if (NS_FAILED(rv)) goto done;
6893 
6894   NS_ENSURE_TRUE(dstServer, NS_ERROR_NULL_POINTER);
6895 
6896   rv = dstServer->Equals(srcServer, &sameServer);
6897   if (NS_FAILED(rv)) goto done;
6898 
6899   // in theory, if allowUndo is true, then this is a user initiated
6900   // action, and we should do it pseudo-offline. If it's not
6901   // user initiated (e.g., mail filters firing), then allowUndo is
6902   // false, and we should just do the action.
6903   if (!WeAreOffline() && sameServer && allowUndo) {
6904     // complete the copy operation as in offline mode
6905     rv = CopyMessagesOffline(srcFolder, messages, isMove, msgWindow, listener);
6906 
6907     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "error offline copy");
6908     // We'll warn if this fails, but we should still try to play back
6909     // offline ops, because it's possible the copy got far enough to
6910     // create the offline ops.
6911 
6912     // We make sure that the source folder is an imap folder by limiting
6913     // pseudo-offline operations to the same imap server. If we extend the code
6914     // to cover non imap folders in the future (i.e. imap folder->local folder),
6915     // then the following downcast will cause either a crash or compiler error.
6916     // Do not forget to change it accordingly.
6917     nsImapMailFolder* srcImapFolder = static_cast<nsImapMailFolder*>(srcFolder);
6918 
6919     // lazily create playback timer if it is not already
6920     // created
6921     if (!srcImapFolder->m_playbackTimer) {
6922       rv = srcImapFolder->CreatePlaybackTimer();
6923       NS_ENSURE_SUCCESS(rv, rv);
6924     }
6925 
6926     if (srcImapFolder->m_playbackTimer) {
6927       // if there is no pending request, create a new one, and set the timer.
6928       // Otherwise use the existing one to reset the timer. it is callback
6929       // function's responsibility to delete the new request object
6930       if (!srcImapFolder->m_pendingPlaybackReq) {
6931         srcImapFolder->m_pendingPlaybackReq =
6932             new nsPlaybackRequest(srcImapFolder, msgWindow);
6933         if (!srcImapFolder->m_pendingPlaybackReq) return NS_ERROR_OUT_OF_MEMORY;
6934       }
6935 
6936       srcImapFolder->m_playbackTimer->InitWithNamedFuncCallback(
6937           PlaybackTimerCallback, (void*)srcImapFolder->m_pendingPlaybackReq,
6938           PLAYBACK_TIMER_INTERVAL_IN_MS, nsITimer::TYPE_ONE_SHOT,
6939           "nsImapMailFolder::PlaybackTimerCallback");
6940     }
6941     return rv;
6942   } else {
6943     // sort the message array by key
6944 
6945     nsTArray<nsMsgKey> keyArray(messages.Length());
6946     for (nsIMsgDBHdr* aMessage : messages) {
6947       if (!aMessage) {
6948         continue;
6949       }
6950       nsMsgKey key;
6951       aMessage->GetMessageKey(&key);
6952       keyArray.AppendElement(key);
6953     }
6954     keyArray.Sort();
6955 
6956     nsTArray<RefPtr<nsIMsgDBHdr>> sortedMsgs;
6957     rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs);
6958     NS_ENSURE_SUCCESS(rv, rv);
6959 
6960     if (WeAreOffline())
6961       return CopyMessagesOffline(srcFolder, sortedMsgs, isMove, msgWindow,
6962                                  listener);
6963 
6964     nsCOMPtr<nsIImapService> imapService =
6965         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
6966     NS_ENSURE_SUCCESS(rv, rv);
6967 
6968     // 3rd parameter: Do not set offline flag.
6969     SetPendingAttributes(sortedMsgs, isMove, false);
6970 
6971     // if the folders aren't on the same server, do a stream base copy
6972     if (!sameServer) {
6973       rv = CopyMessagesWithStream(srcFolder, sortedMsgs, isMove, true,
6974                                   msgWindow, listener, allowUndo);
6975       goto done;
6976     }
6977 
6978     nsAutoCString messageIds;
6979     rv = AllocateUidStringFromKeys(keyArray, messageIds);
6980     if (NS_FAILED(rv)) goto done;
6981 
6982     nsCOMPtr<nsIUrlListener> urlListener;
6983     rv =
6984         QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
6985     rv = InitCopyState(srcSupport, sortedMsgs, isMove, true, false, 0,
6986                        EmptyCString(), listener, msgWindow, allowUndo);
6987     if (NS_FAILED(rv)) goto done;
6988 
6989     m_copyState->m_curIndex = m_copyState->m_messages.Length();
6990 
6991     if (isMove)
6992       srcFolder->EnableNotifications(
6993           allMessageCountNotifications,
6994           false);  // disable message count notification
6995 
6996     nsCOMPtr<nsISupports> copySupport = do_QueryInterface(m_copyState);
6997     rv = imapService->OnlineMessageCopy(srcFolder, messageIds, this, true,
6998                                         isMove, urlListener, nullptr,
6999                                         copySupport, msgWindow);
7000     if (NS_SUCCEEDED(rv) && m_copyState->m_allowUndo) {
7001       RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
7002       if (!undoMsgTxn ||
7003           NS_FAILED(undoMsgTxn->Init(srcFolder, &keyArray, messageIds.get(),
7004                                      this, true, isMove)))
7005         return NS_ERROR_OUT_OF_MEMORY;
7006 
7007       if (isMove) {
7008         if (mFlags & nsMsgFolderFlags::Trash)
7009           undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
7010         else
7011           undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
7012       } else
7013         undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
7014       m_copyState->m_undoMsgTxn = undoMsgTxn;
7015     }
7016 
7017   }  // endif
7018 
7019 done:
7020   if (NS_FAILED(rv)) {
7021     (void)OnCopyCompleted(srcSupport, rv);
7022     if (isMove) {
7023       srcFolder->EnableNotifications(
7024           allMessageCountNotifications,
7025           true);  // enable message count notification
7026       NotifyFolderEvent(kDeleteOrMoveMsgFailed);
7027     }
7028   }
7029   return rv;
7030 }
7031 
7032 class nsImapFolderCopyState final : public nsIUrlListener,
7033                                     public nsIMsgCopyServiceListener {
7034  public:
7035   nsImapFolderCopyState(nsIMsgFolder* destParent, nsIMsgFolder* srcFolder,
7036                         bool isMoveFolder, nsIMsgWindow* msgWindow,
7037                         nsIMsgCopyServiceListener* listener);
7038 
7039   NS_DECL_ISUPPORTS
7040   NS_DECL_NSIURLLISTENER
7041   NS_DECL_NSIMSGCOPYSERVICELISTENER
7042 
7043   nsresult StartNextCopy();
7044   nsresult AdvanceToNextFolder(nsresult aStatus);
7045 
7046  protected:
7047   ~nsImapFolderCopyState();
7048   RefPtr<nsImapMailFolder> m_newDestFolder;
7049   nsCOMPtr<nsISupports> m_origSrcFolder;
7050   nsCOMPtr<nsIMsgFolder> m_curDestParent;
7051   nsCOMPtr<nsIMsgFolder> m_curSrcFolder;
7052   bool m_isMoveFolder;
7053   nsCOMPtr<nsIMsgCopyServiceListener> m_copySrvcListener;
7054   nsCOMPtr<nsIMsgWindow> m_msgWindow;
7055   int32_t m_childIndex;
7056   nsCOMArray<nsIMsgFolder> m_srcChildFolders;
7057   nsCOMArray<nsIMsgFolder> m_destParents;
7058 };
7059 
NS_IMPL_ISUPPORTS(nsImapFolderCopyState,nsIUrlListener,nsIMsgCopyServiceListener)7060 NS_IMPL_ISUPPORTS(nsImapFolderCopyState, nsIUrlListener,
7061                   nsIMsgCopyServiceListener)
7062 
7063 nsImapFolderCopyState::nsImapFolderCopyState(
7064     nsIMsgFolder* destParent, nsIMsgFolder* srcFolder, bool isMoveFolder,
7065     nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) {
7066   m_origSrcFolder = do_QueryInterface(srcFolder);
7067   m_curDestParent = destParent;
7068   m_curSrcFolder = srcFolder;
7069   m_isMoveFolder = isMoveFolder;
7070   m_msgWindow = msgWindow;
7071   m_copySrvcListener = listener;
7072   m_childIndex = -1;
7073   // NOTE: The nsImapMailFolder doesn't keep a reference to us, so we're
7074   // relying on our use as a listener by nsImapService and nsMsgCopyService
7075   // to keep our refcount from zeroing!
7076   // Might be safer to add a kungfudeathgrip on ourselves for the duration
7077   // of the operation? Would need to make sure we catch all error conditions.
7078 }
7079 
~nsImapFolderCopyState()7080 nsImapFolderCopyState::~nsImapFolderCopyState() {}
7081 
StartNextCopy()7082 nsresult nsImapFolderCopyState::StartNextCopy() {
7083   nsresult rv;
7084   // Create the destination folder (our OnStopRunningUrl() will be called
7085   // when done).
7086   nsCOMPtr<nsIImapService> imapService =
7087       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
7088   NS_ENSURE_SUCCESS(rv, rv);
7089   nsString folderName;
7090   m_curSrcFolder->GetName(folderName);
7091   return imapService->EnsureFolderExists(m_curDestParent, folderName,
7092                                          m_msgWindow, this, nullptr);
7093 }
7094 
AdvanceToNextFolder(nsresult aStatus)7095 nsresult nsImapFolderCopyState::AdvanceToNextFolder(nsresult aStatus) {
7096   nsresult rv = NS_OK;
7097   m_childIndex++;
7098   if (m_childIndex >= m_srcChildFolders.Count()) {
7099     if (m_newDestFolder)
7100       m_newDestFolder->OnCopyCompleted(m_origSrcFolder, aStatus);
7101   } else {
7102     m_curDestParent = m_destParents[m_childIndex];
7103     m_curSrcFolder = m_srcChildFolders[m_childIndex];
7104     rv = StartNextCopy();
7105   }
7106   return rv;
7107 }
7108 
7109 NS_IMETHODIMP
OnStartRunningUrl(nsIURI * aUrl)7110 nsImapFolderCopyState::OnStartRunningUrl(nsIURI* aUrl) {
7111   NS_ASSERTION(aUrl, "sanity check - need to be be running non-null url");
7112   return NS_OK;
7113 }
7114 
7115 NS_IMETHODIMP
OnStopRunningUrl(nsIURI * aUrl,nsresult aExitCode)7116 nsImapFolderCopyState::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
7117   if (NS_FAILED(aExitCode)) {
7118     if (m_copySrvcListener) m_copySrvcListener->OnStopCopy(aExitCode);
7119     return aExitCode;  // or NS_OK???
7120   }
7121   nsresult rv = NS_OK;
7122   if (aUrl) {
7123     nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl);
7124     if (imapUrl) {
7125       nsImapAction imapAction = nsIImapUrl::nsImapTest;
7126       imapUrl->GetImapAction(&imapAction);
7127 
7128       switch (imapAction) {
7129         case nsIImapUrl::nsImapEnsureExistsFolder: {
7130           // Our EnsureFolderExists() call has completed successfully,
7131           // so our dest folder is ready.
7132           nsCOMPtr<nsIMsgFolder> newMsgFolder;
7133           nsString folderName;
7134           nsCString utfLeafName;
7135           m_curSrcFolder->GetName(folderName);
7136           bool utf8AcceptEnabled;
7137           nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
7138               do_QueryInterface(m_curDestParent);
7139           rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled);
7140           NS_ENSURE_SUCCESS(rv, rv);
7141           if (utf8AcceptEnabled) {
7142             CopyUTF16toUTF8(folderName, utfLeafName);
7143           } else {
7144             CopyUTF16toMUTF7(folderName, utfLeafName);
7145           }
7146           // Create the nsIMsgFolder object which represents the folder on
7147           // the IMAP server.
7148           rv = m_curDestParent->FindSubFolder(utfLeafName,
7149                                               getter_AddRefs(newMsgFolder));
7150           NS_ENSURE_SUCCESS(rv, rv);
7151           // Save the first new folder so we can send a notification to the
7152           // copy service when this whole process is done.
7153           if (!m_newDestFolder)
7154             m_newDestFolder =
7155                 static_cast<nsImapMailFolder*>(newMsgFolder.get());
7156 
7157           // Check if the source folder has children. If it does, list them
7158           // into m_srcChildFolders, and set m_destParents for the
7159           // corresponding indexes to the newly created folder.
7160           nsTArray<RefPtr<nsIMsgFolder>> subFolders;
7161           rv = m_curSrcFolder->GetSubFolders(subFolders);
7162           NS_ENSURE_SUCCESS(rv, rv);
7163           uint32_t childIndex = 0;
7164           for (nsIMsgFolder* folder : subFolders) {
7165             m_srcChildFolders.InsertElementAt(m_childIndex + childIndex + 1,
7166                                               folder);
7167             m_destParents.InsertElementAt(m_childIndex + childIndex + 1,
7168                                           newMsgFolder);
7169             ++childIndex;
7170           }
7171 
7172           // Now kick off a copy (or move) of messages to the new folder.
7173           nsCOMPtr<nsIMsgEnumerator> enumerator;
7174           rv = m_curSrcFolder->GetMessages(getter_AddRefs(enumerator));
7175           nsTArray<RefPtr<nsIMsgDBHdr>> msgArray;
7176           bool hasMore = false;
7177 
7178           if (enumerator) rv = enumerator->HasMoreElements(&hasMore);
7179 
7180           // Early-out for empty folder.
7181           if (!hasMore) return AdvanceToNextFolder(NS_OK);
7182 
7183           while (NS_SUCCEEDED(rv) && hasMore) {
7184             nsCOMPtr<nsIMsgDBHdr> hdr;
7185             rv = enumerator->GetNext(getter_AddRefs(hdr));
7186             NS_ENSURE_SUCCESS(rv, rv);
7187             msgArray.AppendElement(hdr);
7188             rv = enumerator->HasMoreElements(&hasMore);
7189           }
7190 
7191           nsCOMPtr<nsIMsgCopyService> copyService =
7192               do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
7193           NS_ENSURE_SUCCESS(rv, rv);
7194           rv = copyService->CopyMessages(m_curSrcFolder, msgArray, newMsgFolder,
7195                                          m_isMoveFolder, this, m_msgWindow,
7196                                          false /* allowUndo */);
7197         } break;
7198       }
7199     }
7200   }
7201   return rv;
7202 }
7203 
OnStartCopy()7204 NS_IMETHODIMP nsImapFolderCopyState::OnStartCopy() { return NS_OK; }
7205 
7206 /* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
OnProgress(uint32_t aProgress,uint32_t aProgressMax)7207 NS_IMETHODIMP nsImapFolderCopyState::OnProgress(uint32_t aProgress,
7208                                                 uint32_t aProgressMax) {
7209   return NS_ERROR_NOT_IMPLEMENTED;
7210 }
7211 
7212 /* void SetMessageKey (in nsMsgKey aKey); */
SetMessageKey(nsMsgKey aKey)7213 NS_IMETHODIMP nsImapFolderCopyState::SetMessageKey(nsMsgKey aKey) {
7214   return NS_ERROR_NOT_IMPLEMENTED;
7215 }
7216 
7217 /* [noscript] void GetMessageId (in nsCString aMessageId); */
GetMessageId(nsACString & messageId)7218 NS_IMETHODIMP nsImapFolderCopyState::GetMessageId(nsACString& messageId) {
7219   return NS_ERROR_NOT_IMPLEMENTED;
7220 }
7221 
7222 /* void OnStopCopy (in nsresult aStatus); */
OnStopCopy(nsresult aStatus)7223 NS_IMETHODIMP nsImapFolderCopyState::OnStopCopy(nsresult aStatus) {
7224   if (NS_SUCCEEDED(aStatus)) return AdvanceToNextFolder(aStatus);
7225   if (m_copySrvcListener) {
7226     (void)m_copySrvcListener->OnStopCopy(aStatus);
7227     m_copySrvcListener = nullptr;
7228   }
7229 
7230   return NS_OK;
7231 }
7232 
7233 // "this" is the parent of the copied folder.
7234 NS_IMETHODIMP
CopyFolder(nsIMsgFolder * srcFolder,bool isMoveFolder,nsIMsgWindow * msgWindow,nsIMsgCopyServiceListener * listener)7235 nsImapMailFolder::CopyFolder(nsIMsgFolder* srcFolder, bool isMoveFolder,
7236                              nsIMsgWindow* msgWindow,
7237                              nsIMsgCopyServiceListener* listener) {
7238   NS_ENSURE_ARG_POINTER(srcFolder);
7239 
7240   nsresult rv = NS_OK;
7241 
7242   if (isMoveFolder)  // move folder permitted when dstFolder and the srcFolder
7243                      // are on same server
7244   {
7245     uint32_t folderFlags = 0;
7246     if (srcFolder) srcFolder->GetFlags(&folderFlags);
7247 
7248     // if our source folder is a virtual folder
7249     if (folderFlags & nsMsgFolderFlags::Virtual) {
7250       nsCOMPtr<nsIMsgFolder> newMsgFolder;
7251       nsString folderName;
7252       srcFolder->GetName(folderName);
7253 
7254       nsAutoString safeFolderName(folderName);
7255       NS_MsgHashIfNecessary(safeFolderName);
7256 
7257       srcFolder->ForceDBClosed();
7258 
7259       nsCOMPtr<nsIFile> oldPathFile;
7260       rv = srcFolder->GetFilePath(getter_AddRefs(oldPathFile));
7261       NS_ENSURE_SUCCESS(rv, rv);
7262 
7263       nsCOMPtr<nsIFile> summaryFile;
7264       GetSummaryFileLocation(oldPathFile, getter_AddRefs(summaryFile));
7265 
7266       nsCOMPtr<nsIFile> newPathFile;
7267       rv = GetFilePath(getter_AddRefs(newPathFile));
7268       NS_ENSURE_SUCCESS(rv, rv);
7269 
7270       bool isDirectory = false;
7271       newPathFile->IsDirectory(&isDirectory);
7272       if (!isDirectory) {
7273         AddDirectorySeparator(newPathFile);
7274         rv = newPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
7275         NS_ENSURE_SUCCESS(rv, rv);
7276       }
7277 
7278       rv = CheckIfFolderExists(folderName, this, msgWindow);
7279       if (NS_FAILED(rv)) return rv;
7280 
7281       rv = summaryFile->CopyTo(newPathFile, EmptyString());
7282       NS_ENSURE_SUCCESS(rv, rv);
7283 
7284       rv = AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder));
7285       NS_ENSURE_SUCCESS(rv, rv);
7286 
7287       newMsgFolder->SetPrettyName(folderName);
7288 
7289       uint32_t flags;
7290       srcFolder->GetFlags(&flags);
7291       newMsgFolder->SetFlags(flags);
7292 
7293       NotifyItemAdded(newMsgFolder);
7294 
7295       // now remove the old folder
7296       nsCOMPtr<nsIMsgFolder> msgParent;
7297       srcFolder->GetParent(getter_AddRefs(msgParent));
7298       srcFolder->SetParent(nullptr);
7299       if (msgParent) {
7300         msgParent->PropagateDelete(
7301             srcFolder, false, msgWindow);  // The files have already been moved,
7302                                            // so delete storage false
7303         oldPathFile->Remove(false);        // berkeley mailbox
7304         srcFolder->DeleteStorage();
7305 
7306         nsCOMPtr<nsIFile> parentPathFile;
7307         rv = msgParent->GetFilePath(getter_AddRefs(parentPathFile));
7308         NS_ENSURE_SUCCESS(rv, rv);
7309 
7310         AddDirectorySeparator(parentPathFile);
7311         nsCOMPtr<nsIDirectoryEnumerator> children;
7312         parentPathFile->GetDirectoryEntries(getter_AddRefs(children));
7313         bool more;
7314         // checks if the directory is empty or not
7315         if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
7316           parentPathFile->Remove(true);
7317       }
7318     } else  // non-virtual folder
7319     {
7320       nsCOMPtr<nsIImapService> imapService =
7321           do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
7322       NS_ENSURE_SUCCESS(rv, rv);
7323       nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
7324       bool match = false;
7325       bool confirmed = false;
7326       if (mFlags & nsMsgFolderFlags::Trash) {
7327         rv = srcFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
7328         if (match) {
7329           srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed);
7330           // should we return an error to copy service?
7331           // or send a notification?
7332           if (!confirmed) return NS_OK;
7333         }
7334       }
7335       rv = InitCopyState(srcSupport, {}, false, false, false, 0, EmptyCString(),
7336                          listener, msgWindow, false);
7337       if (NS_FAILED(rv)) return OnCopyCompleted(srcSupport, rv);
7338 
7339       rv = imapService->MoveFolder(srcFolder, this, this, msgWindow, nullptr);
7340     }
7341   } else  // copying folder (should only be across server?)
7342   {
7343     // NOTE: the copystate object must hold itself in existence until complete,
7344     // as we're not keeping hold of it here.
7345     RefPtr<nsImapFolderCopyState> folderCopier = new nsImapFolderCopyState(
7346         this, srcFolder, isMoveFolder, msgWindow, listener);
7347     return folderCopier->StartNextCopy();
7348   }
7349   return rv;
7350 }
7351 
7352 NS_IMETHODIMP
CopyFileMessage(nsIFile * file,nsIMsgDBHdr * msgToReplace,bool isDraftOrTemplate,uint32_t aNewMsgFlags,const nsACString & aNewMsgKeywords,nsIMsgWindow * msgWindow,nsIMsgCopyServiceListener * listener)7353 nsImapMailFolder::CopyFileMessage(nsIFile* file, nsIMsgDBHdr* msgToReplace,
7354                                   bool isDraftOrTemplate, uint32_t aNewMsgFlags,
7355                                   const nsACString& aNewMsgKeywords,
7356                                   nsIMsgWindow* msgWindow,
7357                                   nsIMsgCopyServiceListener* listener) {
7358   nsresult rv = NS_ERROR_NULL_POINTER;
7359   nsMsgKey key = nsMsgKey_None;
7360   nsAutoCString messageId;
7361   nsTArray<RefPtr<nsIMsgDBHdr>> messages;
7362 
7363   nsCOMPtr<nsIImapService> imapService =
7364       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
7365   if (NS_FAILED(rv)) return OnCopyCompleted(file, rv);
7366 
7367   if (msgToReplace) {
7368     rv = msgToReplace->GetMessageKey(&key);
7369     if (NS_SUCCEEDED(rv)) {
7370       messageId.AppendInt((int32_t)key);
7371       // Perhaps we have the message offline, but even if we do it is
7372       // not valid, since the only time we do a file copy for an
7373       // existing message is when we are changing the message.
7374       // So set the offline size to 0 to force SetPendingAttributes to
7375       // clear the offline message flag.
7376       msgToReplace->SetOfflineMessageSize(0);
7377       messages.AppendElement(msgToReplace);
7378       SetPendingAttributes({msgToReplace}, false, false);
7379     }
7380   }
7381 
7382   bool isMove = (msgToReplace ? true : false);
7383   rv = InitCopyState(file, messages, isMove, isDraftOrTemplate, false,
7384                      aNewMsgFlags, aNewMsgKeywords, listener, msgWindow, false);
7385   if (NS_FAILED(rv)) return OnCopyCompleted(file, rv);
7386 
7387   m_copyState->m_streamCopy = true;
7388   rv = imapService->AppendMessageFromFile(file, this, messageId, true,
7389                                           isDraftOrTemplate, this, nullptr,
7390                                           m_copyState, msgWindow);
7391   if (NS_FAILED(rv)) return OnCopyCompleted(file, rv);
7392 
7393   return rv;
7394 }
7395 
CopyStreamMessage(nsIMsgDBHdr * message,nsIMsgFolder * dstFolder,nsIMsgWindow * aMsgWindow,bool isMove)7396 nsresult nsImapMailFolder::CopyStreamMessage(
7397     nsIMsgDBHdr* message,
7398     nsIMsgFolder* dstFolder,  // should be this
7399     nsIMsgWindow* aMsgWindow, bool isMove) {
7400   NS_ENSURE_ARG_POINTER(message);
7401   if (!m_copyState)
7402     MOZ_LOG(IMAP, mozilla::LogLevel::Info,
7403             ("CopyStreamMessage failed with null m_copyState"));
7404   NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER);
7405   nsresult rv;
7406   nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener =
7407       do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv);
7408   NS_ENSURE_SUCCESS(rv, rv);
7409 
7410   nsCOMPtr<nsICopyMessageListener> copyListener(
7411       do_QueryInterface(dstFolder, &rv));
7412   NS_ENSURE_SUCCESS(rv, rv);
7413 
7414   nsCOMPtr<nsIMsgFolder> srcFolder(
7415       do_QueryInterface(m_copyState->m_srcSupport, &rv));
7416   if (NS_FAILED(rv))
7417     MOZ_LOG(IMAP, mozilla::LogLevel::Info,
7418             ("CopyStreaMessage failed with null m_copyState->m_srcSupport"));
7419   if (NS_FAILED(rv)) return rv;
7420   rv = copyStreamListener->Init(srcFolder, copyListener, nullptr);
7421   if (NS_FAILED(rv))
7422     MOZ_LOG(IMAP, mozilla::LogLevel::Info,
7423             ("CopyStreaMessage failed in copyStreamListener->Init"));
7424   if (NS_FAILED(rv)) return rv;
7425 
7426   nsCString uri;
7427   srcFolder->GetUriForMsg(message, uri);
7428 
7429   if (!m_copyState->m_msgService)
7430     rv = GetMessageServiceFromURI(uri,
7431                                   getter_AddRefs(m_copyState->m_msgService));
7432 
7433   if (NS_SUCCEEDED(rv) && m_copyState->m_msgService) {
7434     nsCOMPtr<nsIStreamListener> streamListener(
7435         do_QueryInterface(copyStreamListener, &rv));
7436     NS_ENSURE_SUCCESS(rv, rv);
7437 
7438     // put up status message here, if copying more than one message.
7439     if (m_copyState->m_messages.Length() > 1) {
7440       nsString dstFolderName, progressText;
7441       GetName(dstFolderName);
7442       nsAutoString curMsgString;
7443       nsAutoString totalMsgString;
7444       totalMsgString.AppendInt((int32_t)m_copyState->m_messages.Length());
7445       curMsgString.AppendInt(m_copyState->m_curIndex + 1);
7446 
7447       AutoTArray<nsString, 3> formatStrings = {curMsgString, totalMsgString,
7448                                                dstFolderName};
7449 
7450       nsCOMPtr<nsIStringBundle> bundle;
7451       rv = IMAPGetStringBundle(getter_AddRefs(bundle));
7452       NS_ENSURE_SUCCESS(rv, rv);
7453       rv = bundle->FormatStringFromName("imapCopyingMessageOf2", formatStrings,
7454                                         progressText);
7455       nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
7456       if (m_copyState->m_msgWindow)
7457         m_copyState->m_msgWindow->GetStatusFeedback(
7458             getter_AddRefs(statusFeedback));
7459       if (statusFeedback) {
7460         statusFeedback->ShowStatusString(progressText);
7461         int32_t percent;
7462         percent = (100 * m_copyState->m_curIndex) /
7463                   (int32_t)m_copyState->m_messages.Length();
7464         statusFeedback->ShowProgress(percent);
7465       }
7466     }
7467     nsCOMPtr<nsIURI> dummyNull;
7468     rv = m_copyState->m_msgService->CopyMessage(
7469         uri, streamListener, isMove && !m_copyState->m_isCrossServerOp, nullptr,
7470         aMsgWindow, getter_AddRefs(dummyNull));
7471     if (NS_FAILED(rv))
7472       MOZ_LOG(IMAP, mozilla::LogLevel::Info,
7473               ("CopyMessage failed: uri %s", uri.get()));
7474   }
7475   return rv;
7476 }
7477 
nsImapMailCopyState()7478 nsImapMailCopyState::nsImapMailCopyState()
7479     : m_isMove(false),
7480       m_selectedState(false),
7481       m_isCrossServerOp(false),
7482       m_curIndex(0),
7483       m_streamCopy(false),
7484       m_dataBuffer(nullptr),
7485       m_dataBufferSize(0),
7486       m_leftOver(0),
7487       m_allowUndo(false),
7488       m_eatLF(false),
7489       m_newMsgFlags(0),
7490       m_appendUID(nsMsgKey_None) {}
7491 
~nsImapMailCopyState()7492 nsImapMailCopyState::~nsImapMailCopyState() {
7493   PR_Free(m_dataBuffer);
7494   if (m_tmpFile) m_tmpFile->Remove(false);
7495 }
7496 
NS_IMPL_ISUPPORTS(nsImapMailCopyState,nsImapMailCopyState)7497 NS_IMPL_ISUPPORTS(nsImapMailCopyState, nsImapMailCopyState)
7498 
7499 nsresult nsImapMailFolder::InitCopyState(
7500     nsISupports* srcSupport, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
7501     bool isMove, bool selectedState, bool acrossServers, uint32_t newMsgFlags,
7502     const nsACString& newMsgKeywords, nsIMsgCopyServiceListener* listener,
7503     nsIMsgWindow* msgWindow, bool allowUndo) {
7504   NS_ENSURE_ARG_POINTER(srcSupport);
7505 
7506   NS_ENSURE_TRUE(!m_copyState, NS_ERROR_FAILURE);
7507 
7508   m_copyState = new nsImapMailCopyState();
7509 
7510   m_copyState->m_isCrossServerOp = acrossServers;
7511   m_copyState->m_srcSupport = srcSupport;
7512 
7513   m_copyState->m_messages = messages.Clone();
7514   if (!m_copyState->m_isCrossServerOp) {
7515     uint32_t numUnread = 0;
7516     for (nsIMsgDBHdr* message : m_copyState->m_messages) {
7517       // if the message is not there, then assume what the caller tells us to.
7518       bool isRead = false;
7519       uint32_t flags;
7520       if (message) {
7521         message->GetFlags(&flags);
7522         isRead = flags & nsMsgMessageFlags::Read;
7523       }
7524       if (!isRead) numUnread++;
7525     }
7526     m_copyState->m_unreadCount = numUnread;
7527   } else {
7528     nsIMsgDBHdr* message = m_copyState->m_messages[m_copyState->m_curIndex];
7529     // if the key is not there, then assume what the caller tells us to.
7530     bool isRead = false;
7531     uint32_t flags;
7532     if (message) {
7533       message->GetFlags(&flags);
7534       isRead = flags & nsMsgMessageFlags::Read;
7535     }
7536     m_copyState->m_unreadCount = (isRead) ? 0 : 1;
7537   }
7538 
7539   m_copyState->m_isMove = isMove;
7540   m_copyState->m_newMsgFlags = newMsgFlags;
7541   m_copyState->m_newMsgKeywords = newMsgKeywords;
7542   m_copyState->m_allowUndo = allowUndo;
7543   m_copyState->m_selectedState = selectedState;
7544   m_copyState->m_msgWindow = msgWindow;
7545   if (listener) m_copyState->m_listener = listener;
7546   return NS_OK;
7547 }
7548 
CopyFileToOfflineStore(nsIFile * srcFile,nsMsgKey msgKey)7549 nsresult nsImapMailFolder::CopyFileToOfflineStore(nsIFile* srcFile,
7550                                                   nsMsgKey msgKey) {
7551   nsresult rv = GetDatabase();
7552   NS_ENSURE_SUCCESS(rv, rv);
7553 
7554   bool storeOffline = (mFlags & nsMsgFolderFlags::Offline) && !WeAreOffline();
7555 
7556   if (msgKey == nsMsgKey_None) {
7557     // To support send filters, we need to store the message in the database
7558     // when it is copied to the FCC folder. In that case, we know the UID of the
7559     // message and therefore have the correct msgKey. In other cases, where
7560     // we don't need the offline message copied, don't add to db.
7561     if (!storeOffline) return NS_OK;
7562 
7563     mDatabase->GetNextFakeOfflineMsgKey(&msgKey);
7564   }
7565 
7566   nsCOMPtr<nsIMsgDBHdr> fakeHdr;
7567   rv = mDatabase->CreateNewHdr(msgKey, getter_AddRefs(fakeHdr));
7568   NS_ENSURE_SUCCESS(rv, rv);
7569   fakeHdr->SetUint32Property("pseudoHdr", 1);
7570 
7571   // Should we add this to the offline store?
7572   nsCOMPtr<nsIOutputStream> offlineStore;
7573   if (storeOffline) {
7574     rv = GetOfflineStoreOutputStream(fakeHdr, getter_AddRefs(offlineStore));
7575     NS_ENSURE_SUCCESS(rv, rv);
7576   }
7577 
7578   // We set an offline kMoveResult because in any case we want to update this
7579   // msgHdr with one downloaded from the server, with possible additional
7580   // headers added.
7581   nsCOMPtr<nsIMsgOfflineImapOperation> op;
7582   rv = mDatabase->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
7583   if (NS_SUCCEEDED(rv) && op) {
7584     nsCString destFolderUri;
7585     GetURI(destFolderUri);
7586     op->SetOperation(nsIMsgOfflineImapOperation::kMoveResult);
7587     op->SetDestinationFolderURI(destFolderUri);
7588     SetFlag(nsMsgFolderFlags::OfflineEvents);
7589   }
7590 
7591   nsCOMPtr<nsIInputStream> inputStream;
7592   nsCOMPtr<nsIMsgParseMailMsgState> msgParser =
7593       do_CreateInstance(NS_PARSEMAILMSGSTATE_CONTRACTID, &rv);
7594   NS_ENSURE_SUCCESS(rv, rv);
7595   msgParser->SetMailDB(mDatabase);
7596 
7597   uint64_t offset = 0;
7598   if (offlineStore) {
7599     // Tell the parser to use the offset that will be in the dest stream,
7600     //   not the temp file.
7601     fakeHdr->GetMessageOffset(&offset);
7602   }
7603   msgParser->SetEnvelopePos(offset);
7604 
7605   rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), srcFile);
7606   if (NS_SUCCEEDED(rv) && inputStream) {
7607     // Now, parse the temp file to (optionally) copy to
7608     // the offline store for the cur folder.
7609     RefPtr<nsMsgLineStreamBuffer> inputStreamBuffer =
7610         new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false);
7611     int64_t fileSize;
7612     srcFile->GetFileSize(&fileSize);
7613     uint32_t bytesWritten;
7614     rv = NS_OK;
7615     msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
7616     msgParser->SetNewMsgHdr(fakeHdr);
7617     bool needMoreData = false;
7618     char* newLine = nullptr;
7619     uint32_t numBytesInLine = 0;
7620     if (offlineStore) {
7621       const char* envelope = "From " CRLF;
7622       offlineStore->Write(envelope, strlen(envelope), &bytesWritten);
7623       fileSize += bytesWritten;
7624     }
7625     do {
7626       newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine,
7627                                                 needMoreData);
7628       if (newLine) {
7629         msgParser->ParseAFolderLine(newLine, numBytesInLine);
7630         if (offlineStore)
7631           rv = offlineStore->Write(newLine, numBytesInLine, &bytesWritten);
7632 
7633         free(newLine);
7634         NS_ENSURE_SUCCESS(rv, rv);
7635       }
7636     } while (newLine);
7637 
7638     msgParser->FinishHeader();
7639     uint32_t resultFlags;
7640     if (offlineStore)
7641       fakeHdr->OrFlags(nsMsgMessageFlags::Offline | nsMsgMessageFlags::Read,
7642                        &resultFlags);
7643     else
7644       fakeHdr->OrFlags(nsMsgMessageFlags::Read, &resultFlags);
7645     if (offlineStore) fakeHdr->SetOfflineMessageSize(fileSize);
7646     mDatabase->AddNewHdrToDB(fakeHdr, true /* notify */);
7647 
7648     // Call FinishNewMessage before setting pending attributes, as in
7649     //   maildir it copies from tmp to cur and may change the storeToken
7650     //   to get a unique filename.
7651     if (offlineStore) {
7652       nsCOMPtr<nsIMsgPluggableStore> msgStore;
7653       GetMsgStore(getter_AddRefs(msgStore));
7654       if (msgStore) msgStore->FinishNewMessage(offlineStore, fakeHdr);
7655     }
7656 
7657     // We are copying from a file to offline store so set offline flag.
7658     SetPendingAttributes({&*fakeHdr}, false, true);
7659 
7660     // Gloda needs this notification to index the fake message.
7661     nsCOMPtr<nsIMsgFolderNotificationService> notifier(
7662         do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
7663     if (notifier) notifier->NotifyMsgsClassified({&*fakeHdr}, false, false);
7664     inputStream->Close();
7665     inputStream = nullptr;
7666   }
7667   if (offlineStore) offlineStore->Close();
7668   return rv;
7669 }
7670 
OnCopyCompleted(nsISupports * srcSupport,nsresult rv)7671 nsresult nsImapMailFolder::OnCopyCompleted(nsISupports* srcSupport,
7672                                            nsresult rv) {
7673   // if it's a file, and the copy succeeded, then fcc the offline
7674   // store, and add a kMoveResult offline op.
7675   if (NS_SUCCEEDED(rv) && m_copyState) {
7676     nsCOMPtr<nsIFile> srcFile(do_QueryInterface(srcSupport));
7677     if (srcFile)
7678       (void)CopyFileToOfflineStore(srcFile, m_copyState->m_appendUID);
7679   }
7680   m_copyState = nullptr;
7681   nsresult result;
7682   nsCOMPtr<nsIMsgCopyService> copyService =
7683       do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &result);
7684   NS_ENSURE_SUCCESS(result, result);
7685   return copyService->NotifyCompletion(srcSupport, this, rv);
7686 }
7687 
CreateBaseMessageURI(const nsACString & aURI)7688 nsresult nsImapMailFolder::CreateBaseMessageURI(const nsACString& aURI) {
7689   return nsCreateImapBaseMessageURI(aURI, mBaseMessageURI);
7690 }
7691 
GetFolderURL(nsACString & aFolderURL)7692 NS_IMETHODIMP nsImapMailFolder::GetFolderURL(nsACString& aFolderURL) {
7693   nsCOMPtr<nsIMsgFolder> rootFolder;
7694   nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
7695   NS_ENSURE_SUCCESS(rv, rv);
7696   rootFolder->GetURI(aFolderURL);
7697   if (rootFolder == this) return NS_OK;
7698 
7699   NS_ASSERTION(mURI.Length() > aFolderURL.Length(),
7700                "Should match with a folder name!");
7701   nsCString escapedName;
7702   MsgEscapeString(Substring(mURI, aFolderURL.Length()),
7703                   nsINetUtil::ESCAPE_URL_PATH, escapedName);
7704   if (escapedName.IsEmpty()) return NS_ERROR_OUT_OF_MEMORY;
7705   aFolderURL.Append(escapedName);
7706   return NS_OK;
7707 }
7708 
GetFolderNeedsSubscribing(bool * bVal)7709 NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsSubscribing(bool* bVal) {
7710   NS_ENSURE_ARG_POINTER(bVal);
7711   *bVal = m_folderNeedsSubscribing;
7712   return NS_OK;
7713 }
7714 
SetFolderNeedsSubscribing(bool bVal)7715 NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsSubscribing(bool bVal) {
7716   m_folderNeedsSubscribing = bVal;
7717   return NS_OK;
7718 }
7719 
GetFolderACL()7720 nsMsgIMAPFolderACL* nsImapMailFolder::GetFolderACL() {
7721   if (!m_folderACL) m_folderACL = new nsMsgIMAPFolderACL(this);
7722   return m_folderACL;
7723 }
7724 
CreateACLRightsStringForFolder(nsAString & rightsString)7725 nsresult nsImapMailFolder::CreateACLRightsStringForFolder(
7726     nsAString& rightsString) {
7727   GetFolderACL();  // lazy create
7728   NS_ENSURE_TRUE(m_folderACL, NS_ERROR_NULL_POINTER);
7729   return m_folderACL->CreateACLRightsString(rightsString);
7730 }
7731 
GetFolderNeedsACLListed(bool * bVal)7732 NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsACLListed(bool* bVal) {
7733   NS_ENSURE_ARG_POINTER(bVal);
7734   bool dontNeedACLListed = !m_folderNeedsACLListed;
7735   // if we haven't acl listed, and it's not a no select folder or the inbox,
7736   //  then we'll list the acl if it's not a namespace.
7737   if (m_folderNeedsACLListed &&
7738       !(mFlags & (nsMsgFolderFlags::ImapNoselect | nsMsgFolderFlags::Inbox)))
7739     GetIsNamespace(&dontNeedACLListed);
7740   *bVal = !dontNeedACLListed;
7741   return NS_OK;
7742 }
7743 
SetFolderNeedsACLListed(bool bVal)7744 NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsACLListed(bool bVal) {
7745   m_folderNeedsACLListed = bVal;
7746   return NS_OK;
7747 }
7748 
GetIsNamespace(bool * aResult)7749 NS_IMETHODIMP nsImapMailFolder::GetIsNamespace(bool* aResult) {
7750   NS_ENSURE_ARG_POINTER(aResult);
7751   nsresult rv = NS_OK;
7752   if (!m_namespace) {
7753 #ifdef DEBUG_bienvenu
7754     // Make sure this isn't causing us to open the database
7755     NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown,
7756                  "hierarchy delimiter not set");
7757 #endif
7758 
7759     nsCString onlineName, serverKey;
7760     GetServerKey(serverKey);
7761     GetOnlineName(onlineName);
7762     char hierarchyDelimiter;
7763     GetHierarchyDelimiter(&hierarchyDelimiter);
7764 
7765     nsCOMPtr<nsIImapHostSessionList> hostSession =
7766         do_GetService(kCImapHostSessionList, &rv);
7767     NS_ENSURE_SUCCESS(rv, rv);
7768     m_namespace = nsImapNamespaceList::GetNamespaceForFolder(
7769         serverKey.get(), onlineName.get(), hierarchyDelimiter);
7770     if (m_namespace == nullptr) {
7771       if (mFlags & nsMsgFolderFlags::ImapOtherUser)
7772         rv = hostSession->GetDefaultNamespaceOfTypeForHost(
7773             serverKey.get(), kOtherUsersNamespace, m_namespace);
7774       else if (mFlags & nsMsgFolderFlags::ImapPublic)
7775         rv = hostSession->GetDefaultNamespaceOfTypeForHost(
7776             serverKey.get(), kPublicNamespace, m_namespace);
7777       else
7778         rv = hostSession->GetDefaultNamespaceOfTypeForHost(
7779             serverKey.get(), kPersonalNamespace, m_namespace);
7780     }
7781     NS_ASSERTION(m_namespace, "failed to get namespace");
7782     if (m_namespace) {
7783       nsImapNamespaceList::SuggestHierarchySeparatorForNamespace(
7784           m_namespace, hierarchyDelimiter);
7785       m_folderIsNamespace = nsImapNamespaceList::GetFolderIsNamespace(
7786           serverKey.get(), onlineName.get(), hierarchyDelimiter, m_namespace);
7787     }
7788   }
7789   *aResult = m_folderIsNamespace;
7790   return rv;
7791 }
7792 
SetIsNamespace(bool isNamespace)7793 NS_IMETHODIMP nsImapMailFolder::SetIsNamespace(bool isNamespace) {
7794   m_folderIsNamespace = isNamespace;
7795   return NS_OK;
7796 }
7797 
ResetNamespaceReferences()7798 NS_IMETHODIMP nsImapMailFolder::ResetNamespaceReferences() {
7799   nsCString serverKey;
7800   nsCString onlineName;
7801   GetServerKey(serverKey);
7802   GetOnlineName(onlineName);
7803   char hierarchyDelimiter;
7804   GetHierarchyDelimiter(&hierarchyDelimiter);
7805   m_namespace = nsImapNamespaceList::GetNamespaceForFolder(
7806       serverKey.get(), onlineName.get(), hierarchyDelimiter);
7807   m_folderIsNamespace = m_namespace ? nsImapNamespaceList::GetFolderIsNamespace(
7808                                           serverKey.get(), onlineName.get(),
7809                                           hierarchyDelimiter, m_namespace)
7810                                     : false;
7811 
7812   nsTArray<RefPtr<nsIMsgFolder>> subFolders;
7813   nsresult rv = GetSubFolders(subFolders);
7814   NS_ENSURE_SUCCESS(rv, rv);
7815 
7816   for (nsIMsgFolder* f : subFolders) {
7817     nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(f, &rv));
7818     NS_ENSURE_SUCCESS(rv, rv);
7819     rv = imapFolder->ResetNamespaceReferences();
7820     NS_ENSURE_SUCCESS(rv, rv);
7821   }
7822   return NS_OK;
7823 }
7824 
FindOnlineSubFolder(const nsACString & targetOnlineName,nsIMsgImapMailFolder ** aResultFolder)7825 NS_IMETHODIMP nsImapMailFolder::FindOnlineSubFolder(
7826     const nsACString& targetOnlineName, nsIMsgImapMailFolder** aResultFolder) {
7827   *aResultFolder = nullptr;
7828   nsresult rv = NS_OK;
7829 
7830   nsCString onlineName;
7831   GetOnlineName(onlineName);
7832 
7833   if (onlineName.Equals(targetOnlineName)) {
7834     return QueryInterface(NS_GET_IID(nsIMsgImapMailFolder),
7835                           (void**)aResultFolder);
7836   }
7837 
7838   nsTArray<RefPtr<nsIMsgFolder>> subFolders;
7839   rv = GetSubFolders(subFolders);
7840   NS_ENSURE_SUCCESS(rv, rv);
7841   for (nsIMsgFolder* f : subFolders) {
7842     nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(f, &rv));
7843     NS_ENSURE_SUCCESS(rv, rv);
7844     rv = imapFolder->FindOnlineSubFolder(targetOnlineName, aResultFolder);
7845     NS_ENSURE_SUCCESS(rv, rv);
7846     if (*aResultFolder) {
7847       return NS_OK;  // Found it!
7848     }
7849   }
7850   return NS_OK;  // Not found.
7851 }
7852 
GetFolderNeedsAdded(bool * bVal)7853 NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsAdded(bool* bVal) {
7854   NS_ENSURE_ARG_POINTER(bVal);
7855   *bVal = m_folderNeedsAdded;
7856   return NS_OK;
7857 }
7858 
SetFolderNeedsAdded(bool bVal)7859 NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsAdded(bool bVal) {
7860   m_folderNeedsAdded = bVal;
7861   return NS_OK;
7862 }
7863 
GetFolderQuotaCommandIssued(bool * aCmdIssued)7864 NS_IMETHODIMP nsImapMailFolder::GetFolderQuotaCommandIssued(bool* aCmdIssued) {
7865   NS_ENSURE_ARG_POINTER(aCmdIssued);
7866   *aCmdIssued = m_folderQuotaCommandIssued;
7867   return NS_OK;
7868 }
7869 
SetFolderQuotaCommandIssued(bool aCmdIssued)7870 NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaCommandIssued(bool aCmdIssued) {
7871   m_folderQuotaCommandIssued = aCmdIssued;
7872   return NS_OK;
7873 }
7874 
SetFolderQuotaData(uint32_t aAction,const nsACString & aFolderQuotaRoot,uint64_t aFolderQuotaUsage,uint64_t aFolderQuotaLimit)7875 NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaData(
7876     uint32_t aAction, const nsACString& aFolderQuotaRoot,
7877     uint64_t aFolderQuotaUsage, uint64_t aFolderQuotaLimit) {
7878   switch (aAction) {
7879     case kInvalidateQuota:
7880       // Reset to initialize evaluation of a new quotaroot imap response. This
7881       // clears any previous array data and marks the quota data for this folder
7882       // invalid.
7883       m_folderQuotaDataIsValid = false;
7884       m_folderQuota.Clear();
7885       break;
7886     case kStoreQuota:
7887       // Store folder's quota data to an array. This will occur zero or more
7888       // times for a folder.
7889       m_folderQuota.AppendElement(new nsMsgQuota(
7890           aFolderQuotaRoot, aFolderQuotaUsage, aFolderQuotaLimit));
7891       break;
7892     case kValidateQuota:
7893       // GETQUOTAROOT command was successful and OK response has occurred. This
7894       // indicates that all the untagged QUOTA responses have occurred so mark
7895       // as valid.
7896       m_folderQuotaDataIsValid = true;
7897       break;
7898     default:
7899       // Called with undefined aAction parameter.
7900       NS_ASSERTION(false, "undefined action");
7901   }
7902   return NS_OK;
7903 }
7904 
7905 // Provide the quota array for status bar notification.
GetQuota(nsTArray<RefPtr<nsIMsgQuota>> & aArray)7906 NS_IMETHODIMP nsImapMailFolder::GetQuota(
7907     nsTArray<RefPtr<nsIMsgQuota>>& aArray) {
7908   if (m_folderQuotaDataIsValid) {
7909     aArray = m_folderQuota.Clone();
7910   }
7911   return NS_OK;
7912 }
7913 
PerformExpand(nsIMsgWindow * aMsgWindow)7914 NS_IMETHODIMP nsImapMailFolder::PerformExpand(nsIMsgWindow* aMsgWindow) {
7915   nsresult rv;
7916   bool usingSubscription = false;
7917   nsCOMPtr<nsIImapIncomingServer> imapServer;
7918   rv = GetImapIncomingServer(getter_AddRefs(imapServer));
7919   NS_ENSURE_SUCCESS(rv, rv);
7920   rv = imapServer->GetUsingSubscription(&usingSubscription);
7921   if (NS_SUCCEEDED(rv) && !usingSubscription) {
7922     nsCOMPtr<nsIImapService> imapService =
7923         do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
7924     NS_ENSURE_SUCCESS(rv, rv);
7925     rv = imapService->DiscoverChildren(this, this, m_onlineFolderName, nullptr);
7926   }
7927   return rv;
7928 }
7929 
RenameClient(nsIMsgWindow * msgWindow,nsIMsgFolder * msgFolder,const nsACString & oldName,const nsACString & newName)7930 NS_IMETHODIMP nsImapMailFolder::RenameClient(nsIMsgWindow* msgWindow,
7931                                              nsIMsgFolder* msgFolder,
7932                                              const nsACString& oldName,
7933                                              const nsACString& newName) {
7934   nsresult rv;
7935   nsCOMPtr<nsIFile> pathFile;
7936   rv = GetFilePath(getter_AddRefs(pathFile));
7937   if (NS_FAILED(rv)) return rv;
7938 
7939   nsCOMPtr<nsIMsgImapMailFolder> oldImapFolder =
7940       do_QueryInterface(msgFolder, &rv);
7941   if (NS_FAILED(rv)) return rv;
7942 
7943   char hierarchyDelimiter = '/';
7944   oldImapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
7945   int32_t boxflags = 0;
7946   oldImapFolder->GetBoxFlags(&boxflags);
7947 
7948   nsAutoString newLeafName;
7949   NS_ConvertUTF8toUTF16 newNameString(newName);
7950   NS_ENSURE_SUCCESS(rv, rv);
7951   newLeafName = newNameString;
7952   nsAutoString folderNameStr;
7953   int32_t folderStart = newLeafName.RFindChar(
7954       '/');  // internal use of hierarchyDelimiter is always '/'
7955   if (folderStart > 0) {
7956     newLeafName = Substring(newNameString, folderStart + 1);
7957     CreateDirectoryForFolder(
7958         getter_AddRefs(pathFile));  // needed when we move a folder to a folder
7959                                     // with no subfolders.
7960   }
7961 
7962   // if we get here, it's really a leaf, and "this" is the parent.
7963   folderNameStr = newLeafName;
7964 
7965   // Create an empty database for this mail folder, set its name from the user
7966   nsCOMPtr<nsIMsgDatabase> mailDBFactory;
7967   nsCOMPtr<nsIMsgFolder> child;
7968   nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
7969 
7970   nsCOMPtr<nsIMsgDBService> msgDBService =
7971       do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
7972   NS_ENSURE_SUCCESS(rv, rv);
7973 
7974   nsCOMPtr<nsIMsgDatabase> unusedDB;
7975   nsCOMPtr<nsIFile> dbFile;
7976 
7977   // warning, path will be changed
7978   rv = CreateFileForDB(folderNameStr, pathFile, getter_AddRefs(dbFile));
7979   NS_ENSURE_SUCCESS(rv, rv);
7980 
7981   // Use openMailDBFromFile() and not OpenFolderDB() here, since we don't use
7982   // the DB.
7983   rv = msgDBService->OpenMailDBFromFile(dbFile, nullptr, true, true,
7984                                         getter_AddRefs(unusedDB));
7985   if (NS_SUCCEEDED(rv) && unusedDB) {
7986     // need to set the folder name
7987     nsCOMPtr<nsIDBFolderInfo> folderInfo;
7988     rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
7989 
7990     // Now let's create the actual new folder
7991     rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child));
7992     if (!child || NS_FAILED(rv)) return rv;
7993     nsAutoString unicodeName;
7994     rv = CopyFolderNameToUTF16(NS_ConvertUTF16toUTF8(folderNameStr),
7995                                unicodeName);
7996     if (NS_SUCCEEDED(rv)) child->SetPrettyName(unicodeName);
7997     imapFolder = do_QueryInterface(child);
7998     if (imapFolder) {
7999       nsAutoCString onlineName(m_onlineFolderName);
8000 
8001       if (!onlineName.IsEmpty()) onlineName.Append(hierarchyDelimiter);
8002       onlineName.Append(NS_ConvertUTF16toUTF8(folderNameStr));
8003       imapFolder->SetVerifiedAsOnlineFolder(true);
8004       imapFolder->SetOnlineName(onlineName);
8005       imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
8006       imapFolder->SetBoxFlags(boxflags);
8007       // store the online name as the mailbox name in the db folder info
8008       // I don't think anyone uses the mailbox name, so we'll use it
8009       // to restore the online name when blowing away an imap db.
8010       if (folderInfo) {
8011         nsAutoString unicodeOnlineName;
8012         CopyUTF8toUTF16(onlineName, unicodeOnlineName);
8013         folderInfo->SetMailboxName(unicodeOnlineName);
8014       }
8015       bool changed = false;
8016       msgFolder->MatchOrChangeFilterDestination(
8017           child, false /*caseInsensitive*/, &changed);
8018       if (changed) msgFolder->AlertFilterChanged(msgWindow);
8019     }
8020     unusedDB->SetSummaryValid(true);
8021     unusedDB->Commit(nsMsgDBCommitType::kLargeCommit);
8022     unusedDB->Close(true);
8023     child->RenameSubFolders(msgWindow, msgFolder);
8024     nsCOMPtr<nsIMsgFolder> msgParent;
8025     msgFolder->GetParent(getter_AddRefs(msgParent));
8026     msgFolder->SetParent(nullptr);
8027     // Reset online status now that the folder is renamed.
8028     nsCOMPtr<nsIMsgImapMailFolder> oldImapFolder = do_QueryInterface(msgFolder);
8029     if (oldImapFolder) oldImapFolder->SetVerifiedAsOnlineFolder(false);
8030     nsCOMPtr<nsIMsgFolderNotificationService> notifier(
8031         do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
8032     if (notifier) notifier->NotifyFolderRenamed(msgFolder, child);
8033 
8034     // Do not propagate the deletion until after we have (synchronously)
8035     // notified all listeners about the rename.  This allows them to access
8036     // properties on the source folder without experiencing failures.
8037     if (msgParent) msgParent->PropagateDelete(msgFolder, true, nullptr);
8038     NotifyItemAdded(child);
8039   }
8040   return rv;
8041 }
8042 
RenameSubFolders(nsIMsgWindow * msgWindow,nsIMsgFolder * oldFolder)8043 NS_IMETHODIMP nsImapMailFolder::RenameSubFolders(nsIMsgWindow* msgWindow,
8044                                                  nsIMsgFolder* oldFolder) {
8045   m_initialized = true;
8046   nsTArray<RefPtr<nsIMsgFolder>> subFolders;
8047   nsresult rv = oldFolder->GetSubFolders(subFolders);
8048   NS_ENSURE_SUCCESS(rv, rv);
8049 
8050   for (nsIMsgFolder* msgFolder : subFolders) {
8051     nsCOMPtr<nsIMsgImapMailFolder> folder(do_QueryInterface(msgFolder, &rv));
8052     NS_ENSURE_SUCCESS(rv, rv);
8053 
8054     char hierarchyDelimiter = '/';
8055     folder->GetHierarchyDelimiter(&hierarchyDelimiter);
8056 
8057     int32_t boxflags;
8058     folder->GetBoxFlags(&boxflags);
8059 
8060     bool verified;
8061     folder->GetVerifiedAsOnlineFolder(&verified);
8062 
8063     nsCOMPtr<nsIFile> oldPathFile;
8064     rv = msgFolder->GetFilePath(getter_AddRefs(oldPathFile));
8065     if (NS_FAILED(rv)) return rv;
8066 
8067     nsCOMPtr<nsIFile> newParentPathFile;
8068     rv = GetFilePath(getter_AddRefs(newParentPathFile));
8069     if (NS_FAILED(rv)) return rv;
8070 
8071     rv = AddDirectorySeparator(newParentPathFile);
8072     nsAutoCString oldLeafName;
8073     oldPathFile->GetNativeLeafName(oldLeafName);
8074     newParentPathFile->AppendNative(oldLeafName);
8075 
8076     nsCOMPtr<nsIFile> newPathFile =
8077         do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
8078     NS_ENSURE_SUCCESS(rv, rv);
8079     newPathFile->InitWithFile(newParentPathFile);
8080 
8081     nsCOMPtr<nsIFile> dbFilePath = newPathFile;
8082 
8083     nsCOMPtr<nsIMsgFolder> child;
8084 
8085     nsString folderName;
8086     rv = msgFolder->GetName(folderName);
8087     if (folderName.IsEmpty() || NS_FAILED(rv)) return rv;
8088 
8089     nsCString utfLeafName;
8090     bool utf8AcceptEnabled;
8091     nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
8092     rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled);
8093     NS_ENSURE_SUCCESS(rv, rv);
8094     if (utf8AcceptEnabled) {
8095       CopyUTF16toUTF8(folderName, utfLeafName);
8096     } else {
8097       CopyUTF16toMUTF7(folderName, utfLeafName);
8098     }
8099 
8100     // XXX : Fix this non-sense by fixing AddSubfolderWithPath
8101     nsAutoString unicodeLeafName;
8102     CopyUTF8toUTF16(utfLeafName, unicodeLeafName);
8103 
8104     rv = AddSubfolderWithPath(unicodeLeafName, dbFilePath,
8105                               getter_AddRefs(child));
8106     if (!child || NS_FAILED(rv)) return rv;
8107 
8108     child->SetName(folderName);
8109     imapFolder = do_QueryInterface(child);
8110     nsCString onlineName;
8111     GetOnlineName(onlineName);
8112     nsAutoCString onlineCName(onlineName);
8113     onlineCName.Append(hierarchyDelimiter);
8114     onlineCName.Append(utfLeafName);
8115     if (imapFolder) {
8116       imapFolder->SetVerifiedAsOnlineFolder(verified);
8117       imapFolder->SetOnlineName(onlineCName);
8118       imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
8119       imapFolder->SetBoxFlags(boxflags);
8120 
8121       bool changed = false;
8122       msgFolder->MatchOrChangeFilterDestination(
8123           child, false /*caseInsensitive*/, &changed);
8124       if (changed) msgFolder->AlertFilterChanged(msgWindow);
8125       child->RenameSubFolders(msgWindow, msgFolder);
8126     }
8127   }
8128   return rv;
8129 }
8130 
IsCommandEnabled(const nsACString & command,bool * result)8131 NS_IMETHODIMP nsImapMailFolder::IsCommandEnabled(const nsACString& command,
8132                                                  bool* result) {
8133   NS_ENSURE_ARG_POINTER(result);
8134   *result = !(WeAreOffline() && (command.EqualsLiteral("cmd_renameFolder") ||
8135                                  command.EqualsLiteral("cmd_compactFolder") ||
8136                                  command.EqualsLiteral("button_compact") ||
8137                                  command.EqualsLiteral("cmd_delete") ||
8138                                  command.EqualsLiteral("button_delete")));
8139   return NS_OK;
8140 }
8141 
8142 NS_IMETHODIMP
GetCanFileMessages(bool * aCanFileMessages)8143 nsImapMailFolder::GetCanFileMessages(bool* aCanFileMessages) {
8144   nsresult rv;
8145   *aCanFileMessages = true;
8146 
8147   nsCOMPtr<nsIMsgIncomingServer> server;
8148   rv = GetServer(getter_AddRefs(server));
8149   if (NS_SUCCEEDED(rv) && server)
8150     rv = server->GetCanFileMessagesOnServer(aCanFileMessages);
8151 
8152   if (*aCanFileMessages)
8153     rv = nsMsgDBFolder::GetCanFileMessages(aCanFileMessages);
8154 
8155   if (*aCanFileMessages) {
8156     bool noSelect;
8157     GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
8158     *aCanFileMessages =
8159         (noSelect) ? false : GetFolderACL()->GetCanIInsertInFolder();
8160     return NS_OK;
8161   }
8162   return rv;
8163 }
8164 
8165 NS_IMETHODIMP
GetCanDeleteMessages(bool * aCanDeleteMessages)8166 nsImapMailFolder::GetCanDeleteMessages(bool* aCanDeleteMessages) {
8167   NS_ENSURE_ARG_POINTER(aCanDeleteMessages);
8168   *aCanDeleteMessages = GetFolderACL()->GetCanIDeleteInFolder();
8169   return NS_OK;
8170 }
8171 
8172 NS_IMETHODIMP
GetPerformingBiff(bool * aPerformingBiff)8173 nsImapMailFolder::GetPerformingBiff(bool* aPerformingBiff) {
8174   NS_ENSURE_ARG_POINTER(aPerformingBiff);
8175   *aPerformingBiff = m_performingBiff;
8176   return NS_OK;
8177 }
8178 
8179 NS_IMETHODIMP
SetPerformingBiff(bool aPerformingBiff)8180 nsImapMailFolder::SetPerformingBiff(bool aPerformingBiff) {
8181   m_performingBiff = aPerformingBiff;
8182   return NS_OK;
8183 }
8184 
8185 NS_IMETHODIMP
SetFilterList(nsIMsgFilterList * aMsgFilterList)8186 nsImapMailFolder::SetFilterList(nsIMsgFilterList* aMsgFilterList) {
8187   m_filterList = aMsgFilterList;
8188   return nsMsgDBFolder::SetFilterList(aMsgFilterList);
8189 }
8190 
GetMoveCoalescer()8191 nsresult nsImapMailFolder::GetMoveCoalescer() {
8192   if (!m_moveCoalescer)
8193     m_moveCoalescer = new nsImapMoveCoalescer(this, nullptr /* msgWindow */);
8194   return NS_OK;
8195 }
8196 
8197 NS_IMETHODIMP
StoreCustomKeywords(nsIMsgWindow * aMsgWindow,const nsACString & aFlagsToAdd,const nsACString & aFlagsToSubtract,const nsTArray<nsMsgKey> & aKeysToStore,nsIURI ** _retval)8198 nsImapMailFolder::StoreCustomKeywords(nsIMsgWindow* aMsgWindow,
8199                                       const nsACString& aFlagsToAdd,
8200                                       const nsACString& aFlagsToSubtract,
8201                                       const nsTArray<nsMsgKey>& aKeysToStore,
8202                                       nsIURI** _retval) {
8203   if (aKeysToStore.IsEmpty()) return NS_OK;
8204   nsresult rv = NS_OK;
8205   if (WeAreOffline()) {
8206     GetDatabase();
8207     if (!mDatabase) return NS_ERROR_UNEXPECTED;
8208     for (auto key : aKeysToStore) {
8209       nsCOMPtr<nsIMsgOfflineImapOperation> op;
8210       nsresult rv2 =
8211           mDatabase->GetOfflineOpForKey(key, true, getter_AddRefs(op));
8212       if (NS_FAILED(rv2)) rv = rv2;
8213       SetFlag(nsMsgFolderFlags::OfflineEvents);
8214       if (NS_SUCCEEDED(rv2) && op) {
8215         if (!aFlagsToAdd.IsEmpty())
8216           op->AddKeywordToAdd(PromiseFlatCString(aFlagsToAdd).get());
8217         if (!aFlagsToSubtract.IsEmpty())
8218           op->AddKeywordToRemove(PromiseFlatCString(aFlagsToSubtract).get());
8219       }
8220     }
8221     mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);  // flush offline ops
8222     return rv;
8223   }
8224 
8225   nsCOMPtr<nsIImapService> imapService(
8226       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv));
8227   NS_ENSURE_SUCCESS(rv, rv);
8228   nsAutoCString msgIds;
8229   AllocateUidStringFromKeys(aKeysToStore, msgIds);
8230   return imapService->StoreCustomKeywords(this, aMsgWindow, aFlagsToAdd,
8231                                           aFlagsToSubtract, msgIds, _retval);
8232 }
8233 
NotifyIfNewMail()8234 NS_IMETHODIMP nsImapMailFolder::NotifyIfNewMail() {
8235   return PerformBiffNotifications();
8236 }
8237 
ShowPreviewText()8238 bool nsImapMailFolder::ShowPreviewText() {
8239   bool showPreviewText = false;
8240   nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
8241   if (prefBranch)
8242     prefBranch->GetBoolPref("mail.biff.alert.show_preview", &showPreviewText);
8243   return showPreviewText;
8244 }
8245 
PlaybackCoalescedOperations()8246 nsresult nsImapMailFolder::PlaybackCoalescedOperations() {
8247   if (m_moveCoalescer) {
8248     nsTArray<nsMsgKey>* junkKeysToClassify = m_moveCoalescer->GetKeyBucket(0);
8249     if (junkKeysToClassify && !junkKeysToClassify->IsEmpty())
8250       StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), "Junk"_ns,
8251                           EmptyCString(), *junkKeysToClassify, nullptr);
8252     junkKeysToClassify->Clear();
8253     nsTArray<nsMsgKey>* nonJunkKeysToClassify =
8254         m_moveCoalescer->GetKeyBucket(1);
8255     if (nonJunkKeysToClassify && !nonJunkKeysToClassify->IsEmpty())
8256       StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), "NonJunk"_ns,
8257                           EmptyCString(), *nonJunkKeysToClassify, nullptr);
8258     nonJunkKeysToClassify->Clear();
8259     return m_moveCoalescer->PlaybackMoves(ShowPreviewText());
8260   }
8261   return NS_OK;  // must not be any coalesced operations
8262 }
8263 
8264 NS_IMETHODIMP
SetJunkScoreForMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aMessages,const nsACString & aJunkScore)8265 nsImapMailFolder::SetJunkScoreForMessages(
8266     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
8267     const nsACString& aJunkScore) {
8268   nsresult rv = nsMsgDBFolder::SetJunkScoreForMessages(aMessages, aJunkScore);
8269   if (NS_SUCCEEDED(rv)) {
8270     nsAutoCString messageIds;
8271     nsTArray<nsMsgKey> keys;
8272     nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
8273     NS_ENSURE_SUCCESS(rv, rv);
8274     StoreCustomKeywords(
8275         nullptr, aJunkScore.EqualsLiteral("0") ? "NonJunk"_ns : "Junk"_ns,
8276         aJunkScore.EqualsLiteral("0") ? "Junk"_ns : "NonJunk"_ns, keys,
8277         nullptr);
8278     if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
8279   }
8280   return rv;
8281 }
8282 
8283 NS_IMETHODIMP
OnMessageClassified(const nsACString & aMsgURI,nsMsgJunkStatus aClassification,uint32_t aJunkPercent)8284 nsImapMailFolder::OnMessageClassified(const nsACString& aMsgURI,
8285                                       nsMsgJunkStatus aClassification,
8286                                       uint32_t aJunkPercent) {
8287   nsCOMPtr<nsIMsgIncomingServer> server;
8288   nsresult rv = GetServer(getter_AddRefs(server));
8289   NS_ENSURE_SUCCESS(rv, rv);
8290 
8291   if (!aMsgURI.IsEmpty())  // not end of batch
8292   {
8293     nsCOMPtr<nsIMsgDBHdr> msgHdr;
8294     rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
8295     NS_ENSURE_SUCCESS(rv, rv);
8296 
8297     nsMsgKey msgKey;
8298     rv = msgHdr->GetMessageKey(&msgKey);
8299     NS_ENSURE_SUCCESS(rv, rv);
8300 
8301     // check if this message needs junk classification
8302 
8303     uint32_t processingFlags;
8304     GetProcessingFlags(msgKey, &processingFlags);
8305 
8306     if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) {
8307       nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification,
8308                                          aJunkPercent);
8309 
8310       GetMoveCoalescer();
8311       if (m_moveCoalescer) {
8312         nsTArray<nsMsgKey>* keysToClassify = m_moveCoalescer->GetKeyBucket(
8313             (aClassification == nsIJunkMailPlugin::JUNK) ? 0 : 1);
8314         NS_ASSERTION(keysToClassify, "error getting key bucket");
8315         if (keysToClassify) keysToClassify->AppendElement(msgKey);
8316       }
8317       if (aClassification == nsIJunkMailPlugin::JUNK) {
8318         nsCOMPtr<nsISpamSettings> spamSettings;
8319         rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
8320         NS_ENSURE_SUCCESS(rv, rv);
8321 
8322         bool markAsReadOnSpam;
8323         (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
8324         if (markAsReadOnSpam) {
8325           m_junkMessagesToMarkAsRead.AppendElement(msgHdr);
8326         }
8327 
8328         bool willMoveMessage = false;
8329 
8330         // don't do the move when we are opening up
8331         // the junk mail folder or the trash folder
8332         // or when manually classifying messages in those folders
8333         if (!(mFlags & nsMsgFolderFlags::Junk ||
8334               mFlags & nsMsgFolderFlags::Trash)) {
8335           bool moveOnSpam;
8336           (void)spamSettings->GetMoveOnSpam(&moveOnSpam);
8337           if (moveOnSpam) {
8338             nsCString spamFolderURI;
8339             rv = spamSettings->GetSpamFolderURI(spamFolderURI);
8340             NS_ENSURE_SUCCESS(rv, rv);
8341 
8342             if (!spamFolderURI.IsEmpty()) {
8343               rv = FindFolder(spamFolderURI, getter_AddRefs(mSpamFolder));
8344               NS_ENSURE_SUCCESS(rv, rv);
8345               if (mSpamFolder) {
8346                 rv = mSpamFolder->SetFlag(nsMsgFolderFlags::Junk);
8347                 NS_ENSURE_SUCCESS(rv, rv);
8348                 mSpamKeysToMove.AppendElement(msgKey);
8349                 willMoveMessage = true;
8350               } else {
8351                 // XXX TODO
8352                 // JUNK MAIL RELATED
8353                 // the listener should do
8354                 // rv = folder->SetFlag(nsMsgFolderFlags::Junk);
8355                 // NS_ENSURE_SUCCESS(rv,rv);
8356                 // if (NS_SUCCEEDED(GetMoveCoalescer())) {
8357                 //   m_moveCoalescer->AddMove(folder, msgKey);
8358                 //   willMoveMessage = true;
8359                 // }
8360                 rv = GetOrCreateJunkFolder(spamFolderURI,
8361                                            nullptr /* aListener */);
8362                 NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateJunkFolder failed");
8363               }
8364             }
8365           }
8366         }
8367         rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage);
8368         NS_ENSURE_SUCCESS(rv, rv);
8369       }
8370     }
8371   }
8372 
8373   else  // end of batch
8374   {
8375     // Parent will apply post bayes filters.
8376     nsMsgDBFolder::OnMessageClassified(EmptyCString(),
8377                                        nsIJunkMailPlugin::UNCLASSIFIED, 0);
8378 
8379     if (!m_junkMessagesToMarkAsRead.IsEmpty()) {
8380       rv = MarkMessagesRead(m_junkMessagesToMarkAsRead, true);
8381       NS_ENSURE_SUCCESS(rv, rv);
8382       m_junkMessagesToMarkAsRead.Clear();
8383     }
8384     if (!mSpamKeysToMove.IsEmpty()) {
8385       GetMoveCoalescer();
8386       for (uint32_t keyIndex = 0; keyIndex < mSpamKeysToMove.Length();
8387            keyIndex++) {
8388         // If an upstream filter moved this message, don't move it here.
8389         nsMsgKey msgKey = mSpamKeysToMove.ElementAt(keyIndex);
8390         nsMsgProcessingFlagType processingFlags;
8391         GetProcessingFlags(msgKey, &processingFlags);
8392         if (!(processingFlags & nsMsgProcessingFlags::FilterToMove)) {
8393           if (m_moveCoalescer && mSpamFolder)
8394             m_moveCoalescer->AddMove(mSpamFolder, msgKey);
8395         } else {
8396           // We don't need the FilterToMove flag anymore.
8397           AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove);
8398         }
8399       }
8400       mSpamKeysToMove.Clear();
8401     }
8402 
8403     // Let's not hold onto the spam folder reference longer than necessary.
8404     mSpamFolder = nullptr;
8405 
8406     bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
8407     PlaybackCoalescedOperations();
8408     // If we are performing biff for this folder, tell the server object
8409     if ((!pendingMoves || !ShowPreviewText()) && m_performingBiff) {
8410       // we don't need to adjust the num new messages in this folder because
8411       // the playback moves code already did that.
8412       (void)PerformBiffNotifications();
8413       server->SetPerformingBiff(false);
8414       m_performingBiff = false;
8415     }
8416   }
8417   return NS_OK;
8418 }
8419 
8420 NS_IMETHODIMP
GetShouldDownloadAllHeaders(bool * aResult)8421 nsImapMailFolder::GetShouldDownloadAllHeaders(bool* aResult) {
8422   NS_ENSURE_ARG_POINTER(aResult);
8423   *aResult = false;
8424   // for just the inbox, we check if the filter list has arbitrary headers.
8425   // for all folders, check if we have a spam plugin that requires all headers
8426   if (mFlags & nsMsgFolderFlags::Inbox) {
8427     nsCOMPtr<nsIMsgFilterList> filterList;
8428     nsresult rv = GetFilterList(nullptr, getter_AddRefs(filterList));
8429     NS_ENSURE_SUCCESS(rv, rv);
8430 
8431     rv = filterList->GetShouldDownloadAllHeaders(aResult);
8432     if (*aResult) return rv;
8433   }
8434   nsCOMPtr<nsIMsgFilterPlugin> filterPlugin;
8435   nsCOMPtr<nsIMsgIncomingServer> server;
8436 
8437   if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))))
8438     server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
8439 
8440   return (filterPlugin) ? filterPlugin->GetShouldDownloadAllHeaders(aResult)
8441                         : NS_OK;
8442 }
8443 
GetTrashFolderName(nsAString & aFolderName)8444 void nsImapMailFolder::GetTrashFolderName(nsAString& aFolderName) {
8445   nsCOMPtr<nsIMsgIncomingServer> server;
8446   nsCOMPtr<nsIImapIncomingServer> imapServer;
8447   nsresult rv;
8448   rv = GetServer(getter_AddRefs(server));
8449   if (NS_FAILED(rv)) return;
8450   imapServer = do_QueryInterface(server, &rv);
8451   if (NS_FAILED(rv)) return;
8452   imapServer->GetTrashFolderName(aFolderName);
8453   return;
8454 }
FetchMsgPreviewText(nsTArray<nsMsgKey> const & aKeysToFetch,bool aLocalOnly,nsIUrlListener * aUrlListener,bool * aAsyncResults)8455 NS_IMETHODIMP nsImapMailFolder::FetchMsgPreviewText(
8456     nsTArray<nsMsgKey> const& aKeysToFetch, bool aLocalOnly,
8457     nsIUrlListener* aUrlListener, bool* aAsyncResults) {
8458   NS_ENSURE_ARG_POINTER(aAsyncResults);
8459 
8460   nsTArray<nsMsgKey> keysToFetchFromServer;
8461 
8462   *aAsyncResults = false;
8463   nsresult rv = NS_OK;
8464 
8465   nsCOMPtr<nsIImapService> imapService =
8466       do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
8467   NS_ENSURE_SUCCESS(rv, rv);
8468   nsCOMPtr<nsIMsgMessageService> msgService =
8469       do_QueryInterface(imapService, &rv);
8470   NS_ENSURE_SUCCESS(rv, rv);
8471 
8472   for (uint32_t i = 0; i < aKeysToFetch.Length(); i++) {
8473     nsCOMPtr<nsIMsgDBHdr> msgHdr;
8474     nsCString prevBody;
8475     rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr));
8476     NS_ENSURE_SUCCESS(rv, rv);
8477     // ignore messages that already have a preview body.
8478     msgHdr->GetStringProperty("preview", getter_Copies(prevBody));
8479     if (!prevBody.IsEmpty()) continue;
8480 
8481     /* check if message is in memory cache or offline store. */
8482     nsCOMPtr<nsIURI> url;
8483     nsCOMPtr<nsIInputStream> inputStream;
8484     nsCString messageUri;
8485     rv = GetUriForMsg(msgHdr, messageUri);
8486     NS_ENSURE_SUCCESS(rv, rv);
8487     rv = msgService->GetUrlForUri(messageUri, nullptr, getter_AddRefs(url));
8488     NS_ENSURE_SUCCESS(rv, rv);
8489 
8490     // Lets look in the offline store.
8491     uint32_t msgFlags;
8492     msgHdr->GetFlags(&msgFlags);
8493     nsMsgKey msgKey;
8494     msgHdr->GetMessageKey(&msgKey);
8495     if (msgFlags & nsMsgMessageFlags::Offline) {
8496       int64_t messageOffset;
8497       uint32_t messageSize;
8498       rv = GetOfflineFileStream(msgKey, &messageOffset, &messageSize,
8499                                 getter_AddRefs(inputStream));
8500       NS_ENSURE_SUCCESS(rv, rv);
8501       rv = GetMsgPreviewTextFromStream(msgHdr, inputStream);
8502       NS_ENSURE_SUCCESS(rv, rv);
8503     } else if (!aLocalOnly) {
8504       keysToFetchFromServer.AppendElement(msgKey);
8505     }
8506   }
8507   if (!keysToFetchFromServer.IsEmpty()) {
8508     uint32_t msgCount = keysToFetchFromServer.Length();
8509     nsAutoCString messageIds;
8510     AllocateImapUidString(keysToFetchFromServer.Elements(), msgCount, nullptr,
8511                           messageIds);
8512     rv = imapService->GetBodyStart(this, aUrlListener, messageIds, 2048,
8513                                    nullptr);
8514     *aAsyncResults = true;  // the preview text will be available async...
8515   }
8516   return NS_OK;
8517 }
8518 
AddKeywordsToMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aMessages,const nsACString & aKeywords)8519 NS_IMETHODIMP nsImapMailFolder::AddKeywordsToMessages(
8520     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
8521     const nsACString& aKeywords) {
8522   nsresult rv = nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords);
8523   if (NS_SUCCEEDED(rv)) {
8524     nsAutoCString messageIds;
8525     nsTArray<nsMsgKey> keys;
8526     rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
8527     NS_ENSURE_SUCCESS(rv, rv);
8528     rv = StoreCustomKeywords(nullptr, aKeywords, EmptyCString(), keys, nullptr);
8529     if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
8530   }
8531   return rv;
8532 }
8533 
RemoveKeywordsFromMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aMessages,const nsACString & aKeywords)8534 NS_IMETHODIMP nsImapMailFolder::RemoveKeywordsFromMessages(
8535     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
8536     const nsACString& aKeywords) {
8537   nsresult rv = nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords);
8538   if (NS_SUCCEEDED(rv)) {
8539     nsAutoCString messageIds;
8540     nsTArray<nsMsgKey> keys;
8541     nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
8542     NS_ENSURE_SUCCESS(rv, rv);
8543     rv = StoreCustomKeywords(nullptr, EmptyCString(), aKeywords, keys, nullptr);
8544     if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
8545   }
8546   return rv;
8547 }
8548 
GetCustomIdentity(nsIMsgIdentity ** aIdentity)8549 NS_IMETHODIMP nsImapMailFolder::GetCustomIdentity(nsIMsgIdentity** aIdentity) {
8550   NS_ENSURE_ARG_POINTER(aIdentity);
8551   if (mFlags & nsMsgFolderFlags::ImapOtherUser) {
8552     nsresult rv;
8553     bool delegateOtherUsersFolders = false;
8554     nsCOMPtr<nsIPrefBranch> prefBranch(
8555         do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
8556     NS_ENSURE_SUCCESS(rv, rv);
8557     prefBranch->GetBoolPref("mail.imap.delegateOtherUsersFolders",
8558                             &delegateOtherUsersFolders);
8559     // if we're automatically delegating other user's folders, we need to
8560     // cons up an e-mail address for the other user. We do that by
8561     // taking the other user's name and the current user's domain name,
8562     // assuming they'll be the same. So, <otherUsersName>@<ourDomain>
8563     if (delegateOtherUsersFolders) {
8564       nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
8565       NS_ENSURE_SUCCESS(rv, rv);
8566       nsCOMPtr<nsIMsgAccountManager> accountManager =
8567           do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
8568       NS_ENSURE_SUCCESS(rv, rv);
8569       nsCOMPtr<nsIMsgIdentity> ourIdentity;
8570       nsCOMPtr<nsIMsgIdentity> retIdentity;
8571       nsCOMPtr<nsIMsgAccount> account;
8572       nsCString foldersUserName;
8573       nsCString ourEmailAddress;
8574 
8575       accountManager->FindAccountForServer(server, getter_AddRefs(account));
8576       NS_ENSURE_SUCCESS(rv, rv);
8577       account->GetDefaultIdentity(getter_AddRefs(ourIdentity));
8578       NS_ENSURE_SUCCESS(rv, rv);
8579       ourIdentity->GetEmail(ourEmailAddress);
8580       int32_t atPos = ourEmailAddress.FindChar('@');
8581       if (atPos != kNotFound) {
8582         nsCString otherUsersEmailAddress;
8583         GetFolderOwnerUserName(otherUsersEmailAddress);
8584         otherUsersEmailAddress.Append(
8585             Substring(ourEmailAddress, atPos, ourEmailAddress.Length()));
8586         nsTArray<RefPtr<nsIMsgIdentity>> identities;
8587         rv = accountManager->GetIdentitiesForServer(server, identities);
8588         NS_ENSURE_SUCCESS(rv, rv);
8589 
8590         for (auto identity : identities) {
8591           if (!identity) continue;
8592           nsCString identityEmail;
8593           identity->GetEmail(identityEmail);
8594           if (identityEmail.Equals(otherUsersEmailAddress)) {
8595             retIdentity = identity;
8596             break;
8597           }
8598         }
8599         if (!retIdentity) {
8600           // create the identity
8601           rv = accountManager->CreateIdentity(getter_AddRefs(retIdentity));
8602           NS_ENSURE_SUCCESS(rv, rv);
8603           retIdentity->SetEmail(otherUsersEmailAddress);
8604           nsCOMPtr<nsIMsgAccount> account;
8605           accountManager->FindAccountForServer(server, getter_AddRefs(account));
8606           NS_ENSURE_SUCCESS(rv, rv);
8607           account->AddIdentity(retIdentity);
8608         }
8609       }
8610       if (retIdentity) {
8611         retIdentity.forget(aIdentity);
8612         return NS_OK;
8613       }
8614     }
8615   }
8616   return nsMsgDBFolder::GetCustomIdentity(aIdentity);
8617 }
8618 
ChangePendingTotal(int32_t aDelta)8619 NS_IMETHODIMP nsImapMailFolder::ChangePendingTotal(int32_t aDelta) {
8620   ChangeNumPendingTotalMessages(aDelta);
8621   if (aDelta > 0) NotifyHasPendingMsgs();
8622   return NS_OK;
8623 }
8624 
NotifyHasPendingMsgs()8625 void nsImapMailFolder::NotifyHasPendingMsgs() {
8626   InitAutoSyncState();
8627   nsresult rv;
8628   nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
8629       do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
8630   if (NS_SUCCEEDED(rv)) autoSyncMgr->OnFolderHasPendingMsgs(m_autoSyncStateObj);
8631 }
8632 
8633 /* void changePendingUnread (in long aDelta); */
ChangePendingUnread(int32_t aDelta)8634 NS_IMETHODIMP nsImapMailFolder::ChangePendingUnread(int32_t aDelta) {
8635   ChangeNumPendingUnread(aDelta);
8636   return NS_OK;
8637 }
8638 
GetServerRecent(int32_t * aServerRecent)8639 NS_IMETHODIMP nsImapMailFolder::GetServerRecent(int32_t* aServerRecent) {
8640   NS_ENSURE_ARG_POINTER(aServerRecent);
8641   *aServerRecent = m_numServerRecentMessages;
8642   return NS_OK;
8643 }
8644 
GetServerTotal(int32_t * aServerTotal)8645 NS_IMETHODIMP nsImapMailFolder::GetServerTotal(int32_t* aServerTotal) {
8646   NS_ENSURE_ARG_POINTER(aServerTotal);
8647   *aServerTotal = m_numServerTotalMessages;
8648   return NS_OK;
8649 }
8650 
GetServerUnseen(int32_t * aServerUnseen)8651 NS_IMETHODIMP nsImapMailFolder::GetServerUnseen(int32_t* aServerUnseen) {
8652   NS_ENSURE_ARG_POINTER(aServerUnseen);
8653   *aServerUnseen = m_numServerUnseenMessages;
8654   return NS_OK;
8655 }
8656 
GetServerNextUID(int32_t * aNextUID)8657 NS_IMETHODIMP nsImapMailFolder::GetServerNextUID(int32_t* aNextUID) {
8658   NS_ENSURE_ARG_POINTER(aNextUID);
8659   *aNextUID = m_nextUID;
8660   return NS_OK;
8661 }
8662 
GetAutoSyncStateObj(nsIAutoSyncState ** autoSyncStateObj)8663 NS_IMETHODIMP nsImapMailFolder::GetAutoSyncStateObj(
8664     nsIAutoSyncState** autoSyncStateObj) {
8665   NS_ENSURE_ARG_POINTER(autoSyncStateObj);
8666 
8667   // create auto-sync state object lazily
8668   InitAutoSyncState();
8669 
8670   NS_IF_ADDREF(*autoSyncStateObj = m_autoSyncStateObj);
8671   return NS_OK;
8672 }
8673 
InitiateAutoSync(nsIUrlListener * aUrlListener)8674 NS_IMETHODIMP nsImapMailFolder::InitiateAutoSync(nsIUrlListener* aUrlListener) {
8675   nsCString folderName;
8676   GetURI(folderName);
8677   MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
8678           ("Updating folder: %s", folderName.get()));
8679 
8680   // HACK: if UpdateFolder finds out that it can't open
8681   // the folder, it doesn't set the url listener and returns
8682   // no error. In this case, we return success from this call
8683   // but the caller never gets a notification on its url listener.
8684   bool canOpenThisFolder = true;
8685   GetCanOpenFolder(&canOpenThisFolder);
8686 
8687   if (!canOpenThisFolder) {
8688     MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
8689             ("Cannot update folder: %s", folderName.get()));
8690     return NS_ERROR_FAILURE;
8691   }
8692 
8693   // create auto-sync state object lazily
8694   InitAutoSyncState();
8695 
8696   // make sure we get the counts from the folder cache.
8697   ReadDBFolderInfo(false);
8698 
8699   nsresult rv = m_autoSyncStateObj->ManageStorageSpace();
8700   NS_ENSURE_SUCCESS(rv, rv);
8701 
8702   int32_t syncState;
8703   m_autoSyncStateObj->GetState(&syncState);
8704   if (syncState == nsAutoSyncState::stUpdateNeeded)
8705     return m_autoSyncStateObj->UpdateFolder();
8706 
8707   // We only want to init the autosyncStateObj server counts the first time
8708   // we update, and update it when the STATUS call finishes. This deals with
8709   // the case where biff is doing a STATUS on a non-inbox folder, which
8710   // can make autosync think the counts aren't changing.
8711   PRTime lastUpdateTime;
8712   m_autoSyncStateObj->GetLastUpdateTime(&lastUpdateTime);
8713   if (!lastUpdateTime)
8714     m_autoSyncStateObj->SetServerCounts(m_numServerTotalMessages,
8715                                         m_numServerRecentMessages,
8716                                         m_numServerUnseenMessages, m_nextUID);
8717   // Issue a STATUS command and see if any counts changed.
8718   m_autoSyncStateObj->SetState(nsAutoSyncState::stStatusIssued);
8719   // The OnStopRunningUrl method of the autosync state obj
8720   // will check if the counts or next uid have changed,
8721   // and if so, will issue an UpdateFolder().
8722   rv = UpdateStatus(m_autoSyncStateObj, nullptr);
8723   NS_ENSURE_SUCCESS(rv, rv);
8724 
8725   // record the last update time
8726   m_autoSyncStateObj->SetLastUpdateTime(PR_Now());
8727 
8728   return NS_OK;
8729 }
8730 
CreatePlaybackTimer()8731 nsresult nsImapMailFolder::CreatePlaybackTimer() {
8732   nsresult rv = NS_OK;
8733   if (!m_playbackTimer) {
8734     m_playbackTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
8735     NS_ASSERTION(
8736         NS_SUCCEEDED(rv),
8737         "failed to create pseudo-offline operation timer in nsImapMailFolder");
8738   }
8739   return rv;
8740 }
8741 
PlaybackTimerCallback(nsITimer * aTimer,void * aClosure)8742 void nsImapMailFolder::PlaybackTimerCallback(nsITimer* aTimer, void* aClosure) {
8743   nsPlaybackRequest* request = static_cast<nsPlaybackRequest*>(aClosure);
8744 
8745   NS_ASSERTION(request->SrcFolder->m_pendingPlaybackReq == request,
8746                "wrong playback request pointer");
8747 
8748   RefPtr<nsImapOfflineSync> offlineSync = new nsImapOfflineSync(
8749       request->MsgWindow, nullptr, request->SrcFolder, true);
8750   if (offlineSync) {
8751     mozilla::DebugOnly<nsresult> rv = offlineSync->ProcessNextOperation();
8752     NS_ASSERTION(NS_SUCCEEDED(rv), "pseudo-offline playback is not successful");
8753   }
8754 
8755   // release request struct
8756   request->SrcFolder->m_pendingPlaybackReq = nullptr;
8757   delete request;
8758 }
8759 
InitAutoSyncState()8760 void nsImapMailFolder::InitAutoSyncState() {
8761   if (!m_autoSyncStateObj) m_autoSyncStateObj = new nsAutoSyncState(this);
8762 }
8763 
HasMsgOffline(nsMsgKey msgKey,bool * _retval)8764 NS_IMETHODIMP nsImapMailFolder::HasMsgOffline(nsMsgKey msgKey, bool* _retval) {
8765   NS_ENSURE_ARG_POINTER(_retval);
8766   *_retval = false;
8767   nsCOMPtr<nsIMsgFolder> msgFolder;
8768   nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(msgFolder));
8769   if (NS_SUCCEEDED(rv) && msgFolder) *_retval = true;
8770   return NS_OK;
8771 }
8772 
GetOfflineMsgFolder(nsMsgKey msgKey,nsIMsgFolder ** aMsgFolder)8773 NS_IMETHODIMP nsImapMailFolder::GetOfflineMsgFolder(nsMsgKey msgKey,
8774                                                     nsIMsgFolder** aMsgFolder) {
8775   // Check if we have the message in the current folder.
8776   NS_ENSURE_ARG_POINTER(aMsgFolder);
8777   nsCOMPtr<nsIMsgFolder> subMsgFolder;
8778   nsresult rv = GetDatabase();
8779   NS_ENSURE_SUCCESS(rv, rv);
8780 
8781   nsCOMPtr<nsIMsgDBHdr> hdr;
8782   rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
8783   if (NS_FAILED(rv)) return rv;
8784 
8785   if (hdr) {
8786     uint32_t msgFlags = 0;
8787     hdr->GetFlags(&msgFlags);
8788     // Check if we already have this message body offline
8789     if ((msgFlags & nsMsgMessageFlags::Offline)) {
8790       NS_IF_ADDREF(*aMsgFolder = this);
8791       return NS_OK;
8792     }
8793   }
8794 
8795   if (!*aMsgFolder) {
8796     // Checking the existence of message in other folders in case of GMail
8797     // Server
8798     bool isGMail;
8799     nsCOMPtr<nsIImapIncomingServer> imapServer;
8800     rv = GetImapIncomingServer(getter_AddRefs(imapServer));
8801     NS_ENSURE_SUCCESS(rv, rv);
8802     rv = imapServer->GetIsGMailServer(&isGMail);
8803     NS_ENSURE_SUCCESS(rv, rv);
8804 
8805     if (isGMail) {
8806       nsCString labels;
8807       nsTArray<nsCString> labelNames;
8808       hdr->GetStringProperty("X-GM-LABELS", getter_Copies(labels));
8809       ParseString(labels, ' ', labelNames);
8810       nsCOMPtr<nsIMsgFolder> rootFolder;
8811       nsCOMPtr<nsIMsgImapMailFolder> subFolder;
8812       for (uint32_t i = 0; i < labelNames.Length(); i++) {
8813         rv = GetRootFolder(getter_AddRefs(rootFolder));
8814         if (NS_SUCCEEDED(rv) && (rootFolder)) {
8815           nsCOMPtr<nsIMsgImapMailFolder> imapRootFolder =
8816               do_QueryInterface(rootFolder);
8817           if (labelNames[i].EqualsLiteral("\"\\\\Draft\""))
8818             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts,
8819                                                 getter_AddRefs(subMsgFolder));
8820           if (labelNames[i].EqualsLiteral("\"\\\\Inbox\""))
8821             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
8822                                                 getter_AddRefs(subMsgFolder));
8823           if (labelNames[i].EqualsLiteral("\"\\\\All Mail\""))
8824             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Archive,
8825                                                 getter_AddRefs(subMsgFolder));
8826           if (labelNames[i].EqualsLiteral("\"\\\\Trash\""))
8827             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
8828                                                 getter_AddRefs(subMsgFolder));
8829           if (labelNames[i].EqualsLiteral("\"\\\\Spam\""))
8830             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Junk,
8831                                                 getter_AddRefs(subMsgFolder));
8832           if (labelNames[i].EqualsLiteral("\"\\\\Sent\""))
8833             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail,
8834                                                 getter_AddRefs(subMsgFolder));
8835           if (labelNames[i].Find("[Imap]/", /* ignoreCase = */ true) !=
8836               kNotFound) {
8837             labelNames[i].ReplaceSubstring("[Imap]/", "");
8838             imapRootFolder->FindOnlineSubFolder(labelNames[i],
8839                                                 getter_AddRefs(subFolder));
8840             subMsgFolder = do_QueryInterface(subFolder);
8841           }
8842           if (!subMsgFolder) {
8843             imapRootFolder->FindOnlineSubFolder(labelNames[i],
8844                                                 getter_AddRefs(subFolder));
8845             subMsgFolder = do_QueryInterface(subFolder);
8846           }
8847           if (subMsgFolder) {
8848             nsCOMPtr<nsIMsgDatabase> db;
8849             subMsgFolder->GetMsgDatabase(getter_AddRefs(db));
8850             if (db) {
8851               nsCOMPtr<nsIMsgDBHdr> retHdr;
8852               nsCString gmMsgID;
8853               hdr->GetStringProperty("X-GM-MSGID", getter_Copies(gmMsgID));
8854               rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(),
8855                                            getter_AddRefs(retHdr));
8856               if (NS_FAILED(rv)) return rv;
8857               if (retHdr) {
8858                 uint32_t gmFlags = 0;
8859                 retHdr->GetFlags(&gmFlags);
8860                 if ((gmFlags & nsMsgMessageFlags::Offline)) {
8861                   subMsgFolder.forget(aMsgFolder);
8862                   // Focus on first positive result.
8863                   return NS_OK;
8864                 }
8865               }
8866             }
8867           }
8868         }
8869       }
8870     }
8871   }
8872   return NS_OK;
8873 }
8874 
GetOfflineFileStream(nsMsgKey msgKey,int64_t * offset,uint32_t * size,nsIInputStream ** aFileStream)8875 NS_IMETHODIMP nsImapMailFolder::GetOfflineFileStream(
8876     nsMsgKey msgKey, int64_t* offset, uint32_t* size,
8877     nsIInputStream** aFileStream) {
8878   NS_ENSURE_ARG(aFileStream);
8879   nsCOMPtr<nsIMsgFolder> offlineFolder;
8880   nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(offlineFolder));
8881   NS_ENSURE_SUCCESS(rv, rv);
8882   if (!offlineFolder) return NS_ERROR_FAILURE;
8883 
8884   rv = GetDatabase();
8885   NS_ENSURE_SUCCESS(rv, rv);
8886 
8887   if (offlineFolder == this)
8888     return nsMsgDBFolder::GetOfflineFileStream(msgKey, offset, size,
8889                                                aFileStream);
8890 
8891   nsCOMPtr<nsIMsgDBHdr> hdr;
8892   rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
8893   NS_ENSURE_SUCCESS(rv, rv);
8894 
8895   nsCString gmMsgID;
8896   hdr->GetStringProperty("X-GM-MSGID", getter_Copies(gmMsgID));
8897   nsCOMPtr<nsIMsgDatabase> db;
8898   offlineFolder->GetMsgDatabase(getter_AddRefs(db));
8899   rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(hdr));
8900   NS_ENSURE_SUCCESS(rv, rv);
8901   if (!hdr) {
8902     return NS_ERROR_FAILURE;
8903   }
8904 
8905   nsMsgKey newMsgKey;
8906   hdr->GetMessageKey(&newMsgKey);
8907   return offlineFolder->GetOfflineFileStream(newMsgKey, offset, size,
8908                                              aFileStream);
8909 }
8910 
GetIncomingServerType(nsACString & serverType)8911 NS_IMETHODIMP nsImapMailFolder::GetIncomingServerType(nsACString& serverType) {
8912   serverType.AssignLiteral("imap");
8913   return NS_OK;
8914 }
8915 
GetShouldUseUtf8FolderName(bool * aUseUTF8)8916 NS_IMETHODIMP nsImapMailFolder::GetShouldUseUtf8FolderName(bool* aUseUTF8) {
8917   *aUseUTF8 = false;
8918   nsCOMPtr<nsIMsgIncomingServer> server;
8919   nsresult rv = GetServer(getter_AddRefs(server));
8920   NS_ENSURE_SUCCESS(rv, rv);
8921   nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server, &rv);
8922   NS_ENSURE_SUCCESS(rv, rv);
8923   imapServer->GetUtf8AcceptEnabled(aUseUTF8);
8924   return NS_OK;
8925 }
8926 
DeleteStoreMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aMessages)8927 void nsImapMailFolder::DeleteStoreMessages(
8928     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages) {
8929   // Delete messages for pluggable stores that do not support compaction.
8930   nsCOMPtr<nsIMsgPluggableStore> offlineStore;
8931   (void)GetMsgStore(getter_AddRefs(offlineStore));
8932 
8933   if (offlineStore) {
8934     bool supportsCompaction;
8935     offlineStore->GetSupportsCompaction(&supportsCompaction);
8936     if (!supportsCompaction) offlineStore->DeleteMessages(aMessages);
8937   }
8938 }
8939 
DeleteStoreMessages(const nsTArray<nsMsgKey> & aMessages)8940 void nsImapMailFolder::DeleteStoreMessages(
8941     const nsTArray<nsMsgKey>& aMessages) {
8942   DeleteStoreMessages(aMessages, this);
8943 }
8944 
DeleteStoreMessages(const nsTArray<nsMsgKey> & aMessages,nsIMsgFolder * aFolder)8945 void nsImapMailFolder::DeleteStoreMessages(const nsTArray<nsMsgKey>& aMessages,
8946                                            nsIMsgFolder* aFolder) {
8947   // Delete messages for pluggable stores that do not support compaction.
8948   NS_ASSERTION(aFolder, "Missing Source Folder");
8949   nsCOMPtr<nsIMsgPluggableStore> offlineStore;
8950   (void)aFolder->GetMsgStore(getter_AddRefs(offlineStore));
8951   if (offlineStore) {
8952     bool supportsCompaction;
8953     offlineStore->GetSupportsCompaction(&supportsCompaction);
8954     if (!supportsCompaction) {
8955       nsCOMPtr<nsIMsgDatabase> db;
8956       aFolder->GetMsgDatabase(getter_AddRefs(db));
8957       nsresult rv = NS_ERROR_FAILURE;
8958       nsTArray<RefPtr<nsIMsgDBHdr>> messages;
8959       if (db) rv = MsgGetHeadersFromKeys(db, aMessages, messages);
8960       if (NS_SUCCEEDED(rv))
8961         offlineStore->DeleteMessages(messages);
8962       else
8963         NS_WARNING("Failed to get database");
8964     }
8965   }
8966 }
8967