1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 // this file implements the nsMsgFilterService interface
7 
8 #include "msgCore.h"
9 #include "nsMsgFilterService.h"
10 #include "nsMsgFilterList.h"
11 #include "nsMsgSearchScopeTerm.h"
12 #include "nsDirectoryServiceDefs.h"
13 #include "nsIPrompt.h"
14 #include "nsIDocShell.h"
15 #include "nsIStringBundle.h"
16 #include "nsIMsgSearchNotify.h"
17 #include "nsIUrlListener.h"
18 #include "nsIMsgCopyServiceListener.h"
19 #include "nsIMsgLocalMailFolder.h"
20 #include "nsIMsgDatabase.h"
21 #include "nsIMsgHdr.h"
22 #include "nsMsgBaseCID.h"
23 #include "nsIMsgCopyService.h"
24 #include "nsIInputStream.h"
25 #include "nsIOutputStream.h"
26 #include "nsISafeOutputStream.h"
27 #include "nsIMsgComposeService.h"
28 #include "nsMsgCompCID.h"
29 #include "nsNetUtil.h"
30 #include "nsMsgUtils.h"
31 #include "nsIMsgMailSession.h"
32 #include "nsIFile.h"
33 #include "nsIMsgFilterCustomAction.h"
34 #include "nsMsgMessageFlags.h"
35 #include "nsIMsgWindow.h"
36 #include "nsIMsgSearchCustomTerm.h"
37 #include "nsIMsgSearchTerm.h"
38 #include "nsIMsgThread.h"
39 #include "nsIMsgFilter.h"
40 #include "nsIMsgOperationListener.h"
41 #include "mozilla/Logging.h"
42 
43 using namespace mozilla;
44 
45 LazyLogModule FILTERLOGMODULE("Filters");
46 
47 #define BREAK_IF_FAILURE(_rv, _text)                                   \
48   if (NS_FAILED(_rv)) {                                                \
49     MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,                          \
50             ("(Post) Filter error: %s", _text));                       \
51     m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text), \
52                                 m_curFilter);                          \
53     NS_WARNING(_text);                                                 \
54     mFinalResult = _rv;                                                \
55     break;                                                             \
56   }
57 
58 #define CONTINUE_IF_FAILURE(_rv, _text)                                     \
59   if (NS_FAILED(_rv)) {                                                     \
60     MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,                             \
61             ("(Post) Filter problem: %s", _text));                          \
62     m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text),      \
63                                 m_curFilter);                               \
64     NS_WARNING(_text);                                                      \
65     mFinalResult = _rv;                                                     \
66     if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution(); \
67     continue;                                                               \
68   }
69 
70 #define BREAK_IF_FALSE(_assertTrue, _text)                             \
71   if (MOZ_UNLIKELY(!(_assertTrue))) {                                  \
72     MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,                          \
73             ("(Post) Filter error: %s", _text));                       \
74     m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text), \
75                                 m_curFilter);                          \
76     NS_WARNING(_text);                                                 \
77     mFinalResult = NS_ERROR_FAILURE;                                   \
78     break;                                                             \
79   }
80 
81 #define CONTINUE_IF_FALSE(_assertTrue, _text)                               \
82   if (MOZ_UNLIKELY(!(_assertTrue))) {                                       \
83     MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,                             \
84             ("(Post) Filter problem: %s", _text));                          \
85     m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text),      \
86                                 m_curFilter);                               \
87     NS_WARNING(_text);                                                      \
88     mFinalResult = NS_ERROR_FAILURE;                                        \
89     if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution(); \
90     continue;                                                               \
91   }
92 
93 #define BREAK_ACTION(_text)                                               \
94   MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,                               \
95           ("(Post) Filter Error: %s", _text));                            \
96   if (loggingEnabled)                                                     \
97     m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text),    \
98                                 m_curFilter);                             \
99   NS_WARNING(_text);                                                      \
100   if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution(); \
101   break;
102 
103 #define BREAK_ACTION_IF_FALSE(_assertTrue, _text) \
104   if (MOZ_UNLIKELY(!(_assertTrue))) {             \
105     finalResult = NS_ERROR_FAILURE;               \
106     BREAK_ACTION(_text);                          \
107   }
108 
109 #define BREAK_ACTION_IF_FAILURE(_rv, _text) \
110   if (NS_FAILED(_rv)) {                     \
111     finalResult = _rv;                      \
112     BREAK_ACTION(_text);                    \
113   }
114 
NS_IMPL_ISUPPORTS(nsMsgFilterService,nsIMsgFilterService)115 NS_IMPL_ISUPPORTS(nsMsgFilterService, nsIMsgFilterService)
116 
117 nsMsgFilterService::nsMsgFilterService() {
118   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, ("nsMsgFilterService"));
119 }
120 
~nsMsgFilterService()121 nsMsgFilterService::~nsMsgFilterService() {
122   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, ("~nsMsgFilterService"));
123 }
124 
OpenFilterList(nsIFile * aFilterFile,nsIMsgFolder * rootFolder,nsIMsgWindow * aMsgWindow,nsIMsgFilterList ** resultFilterList)125 NS_IMETHODIMP nsMsgFilterService::OpenFilterList(
126     nsIFile* aFilterFile, nsIMsgFolder* rootFolder, nsIMsgWindow* aMsgWindow,
127     nsIMsgFilterList** resultFilterList) {
128   NS_ENSURE_ARG_POINTER(aFilterFile);
129   NS_ENSURE_ARG_POINTER(resultFilterList);
130 
131   if (rootFolder) {
132     nsCOMPtr<nsIMsgIncomingServer> server;
133     rootFolder->GetServer(getter_AddRefs(server));
134     nsString serverName;
135     server->GetPrettyName(serverName);
136     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
137             ("Reading filter list for account '%s'",
138              NS_ConvertUTF16toUTF8(serverName).get()));
139   }
140 
141   nsString fileName;
142   (void)aFilterFile->GetPath(fileName);
143   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
144           ("Reading filter list from file '%s'",
145            NS_ConvertUTF16toUTF8(fileName).get()));
146 
147   bool exists = false;
148   nsresult rv = aFilterFile->Exists(&exists);
149   if (NS_FAILED(rv) || !exists) {
150     rv = aFilterFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
151     NS_ENSURE_SUCCESS(rv, rv);
152   }
153 
154   nsCOMPtr<nsIInputStream> fileStream;
155   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), aFilterFile);
156   NS_ENSURE_SUCCESS(rv, rv);
157   NS_ENSURE_TRUE(fileStream, NS_ERROR_OUT_OF_MEMORY);
158 
159   RefPtr<nsMsgFilterList> filterList = new nsMsgFilterList();
160   filterList->SetFolder(rootFolder);
161 
162   // temporarily tell the filter where its file path is
163   filterList->SetDefaultFile(aFilterFile);
164 
165   int64_t size = 0;
166   rv = aFilterFile->GetFileSize(&size);
167   if (NS_SUCCEEDED(rv) && size > 0)
168     rv = filterList->LoadTextFilters(fileStream.forget());
169   if (NS_SUCCEEDED(rv)) {
170     int16_t version;
171     filterList->GetVersion(&version);
172     if (version != kFileVersion) SaveFilterList(filterList, aFilterFile);
173   } else {
174     if (rv == NS_MSG_FILTER_PARSE_ERROR && aMsgWindow) {
175       rv = BackUpFilterFile(aFilterFile, aMsgWindow);
176       NS_ENSURE_SUCCESS(rv, rv);
177       rv = aFilterFile->SetFileSize(0);
178       NS_ENSURE_SUCCESS(rv, rv);
179       return OpenFilterList(aFilterFile, rootFolder, aMsgWindow,
180                             resultFilterList);
181     } else if (rv == NS_MSG_CUSTOM_HEADERS_OVERFLOW && aMsgWindow)
182       ThrowAlertMsg("filterCustomHeaderOverflow", aMsgWindow);
183     else if (rv == NS_MSG_INVALID_CUSTOM_HEADER && aMsgWindow)
184       ThrowAlertMsg("invalidCustomHeader", aMsgWindow);
185   }
186 
187   nsCString listId;
188   filterList->GetListId(listId);
189   uint32_t filterCount = 0;
190   (void)filterList->GetFilterCount(&filterCount);
191   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
192           ("Read %" PRIu32 " filters", filterCount));
193   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
194           ("Filter list stored as %s", listId.get()));
195 
196   filterList.forget(resultFilterList);
197   return rv;
198 }
199 
CloseFilterList(nsIMsgFilterList * filterList)200 NS_IMETHODIMP nsMsgFilterService::CloseFilterList(
201     nsIMsgFilterList* filterList) {
202   // NS_ASSERTION(false,"CloseFilterList doesn't do anything yet");
203   return NS_OK;
204 }
205 
206 /* save without deleting */
SaveFilterList(nsIMsgFilterList * filterList,nsIFile * filterFile)207 NS_IMETHODIMP nsMsgFilterService::SaveFilterList(nsIMsgFilterList* filterList,
208                                                  nsIFile* filterFile) {
209   NS_ENSURE_ARG_POINTER(filterFile);
210   NS_ENSURE_ARG_POINTER(filterList);
211 
212   nsCString listId;
213   filterList->GetListId(listId);
214   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
215           ("Saving filter list %s", listId.get()));
216 
217   nsCOMPtr<nsIOutputStream> strm;
218   nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(strm),
219                                                    filterFile, -1, 0600);
220   NS_ENSURE_SUCCESS(rv, rv);
221 
222   rv = filterList->SaveToFile(strm);
223 
224   nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(strm);
225   NS_ASSERTION(safeStream, "expected a safe output stream!");
226   if (safeStream) {
227     rv = safeStream->Finish();
228     if (NS_FAILED(rv)) {
229       NS_WARNING("failed to save filter file! possible data loss");
230       MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, ("Save of list failed"));
231     }
232   }
233   return rv;
234 }
235 
CancelFilterList(nsIMsgFilterList * filterList)236 NS_IMETHODIMP nsMsgFilterService::CancelFilterList(
237     nsIMsgFilterList* filterList) {
238   return NS_ERROR_NOT_IMPLEMENTED;
239 }
240 
BackUpFilterFile(nsIFile * aFilterFile,nsIMsgWindow * aMsgWindow)241 nsresult nsMsgFilterService::BackUpFilterFile(nsIFile* aFilterFile,
242                                               nsIMsgWindow* aMsgWindow) {
243   AlertBackingUpFilterFile(aMsgWindow);
244 
245   nsCOMPtr<nsIFile> localParentDir;
246   nsresult rv = aFilterFile->GetParent(getter_AddRefs(localParentDir));
247   NS_ENSURE_SUCCESS(rv, rv);
248 
249   // if back-up file exists delete the back up file otherwise copy fails.
250   nsCOMPtr<nsIFile> backupFile;
251   rv = localParentDir->Clone(getter_AddRefs(backupFile));
252   NS_ENSURE_SUCCESS(rv, rv);
253   backupFile->AppendNative("rulesbackup.dat"_ns);
254   bool exists;
255   backupFile->Exists(&exists);
256   if (exists) backupFile->Remove(false);
257 
258   return aFilterFile->CopyToNative(localParentDir, "rulesbackup.dat"_ns);
259 }
260 
AlertBackingUpFilterFile(nsIMsgWindow * aMsgWindow)261 nsresult nsMsgFilterService::AlertBackingUpFilterFile(
262     nsIMsgWindow* aMsgWindow) {
263   return ThrowAlertMsg("filterListBackUpMsg", aMsgWindow);
264 }
265 
266 // Do not use this routine if you have to call it very often because it creates
267 // a new bundle each time.
GetStringFromBundle(const char * aMsgName,nsAString & aResult)268 nsresult nsMsgFilterService::GetStringFromBundle(const char* aMsgName,
269                                                  nsAString& aResult) {
270   nsCOMPtr<nsIStringBundle> bundle;
271   nsresult rv = GetFilterStringBundle(getter_AddRefs(bundle));
272   if (NS_SUCCEEDED(rv) && bundle)
273     rv = bundle->GetStringFromName(aMsgName, aResult);
274   return rv;
275 }
276 
GetFilterStringBundle(nsIStringBundle ** aBundle)277 nsresult nsMsgFilterService::GetFilterStringBundle(nsIStringBundle** aBundle) {
278   NS_ENSURE_ARG_POINTER(aBundle);
279 
280   nsCOMPtr<nsIStringBundleService> bundleService =
281       mozilla::services::GetStringBundleService();
282   NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
283   nsCOMPtr<nsIStringBundle> bundle;
284   if (bundleService)
285     bundleService->CreateBundle("chrome://messenger/locale/filter.properties",
286                                 getter_AddRefs(bundle));
287   bundle.forget(aBundle);
288   return NS_OK;
289 }
290 
ThrowAlertMsg(const char * aMsgName,nsIMsgWindow * aMsgWindow)291 nsresult nsMsgFilterService::ThrowAlertMsg(const char* aMsgName,
292                                            nsIMsgWindow* aMsgWindow) {
293   nsString alertString;
294   nsresult rv = GetStringFromBundle(aMsgName, alertString);
295   nsCOMPtr<nsIMsgWindow> msgWindow = aMsgWindow;
296   if (!msgWindow) {
297     nsCOMPtr<nsIMsgMailSession> mailSession(
298         do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
299     if (NS_SUCCEEDED(rv))
300       rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
301   }
302 
303   if (NS_SUCCEEDED(rv) && !alertString.IsEmpty() && msgWindow) {
304     nsCOMPtr<nsIDocShell> docShell;
305     msgWindow->GetRootDocShell(getter_AddRefs(docShell));
306     if (docShell) {
307       nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
308       if (dialog && !alertString.IsEmpty())
309         dialog->Alert(nullptr, alertString.get());
310     }
311   }
312   return rv;
313 }
314 
315 // this class is used to run filters after the fact, i.e., after new mail has
316 // been downloaded from the server. It can do the following:
317 // 1. Apply a single imap or pop3 filter on a single folder.
318 // 2. Apply multiple filters on a single imap or pop3 folder.
319 // 3. Apply a single filter on multiple imap or pop3 folders in the same
320 //    account.
321 // 4. Apply multiple filters on multiple imap or pop3 folders in the same
322 //    account.
323 // This will be called from the front end js code in the case of the
324 // apply filters to folder menu code, and from the filter dialog js code with
325 // the run filter now command.
326 
327 // this class holds the list of filters and folders, and applies them in turn,
328 // first iterating over all the filters on one folder, and then advancing to the
329 // next folder and repeating. For each filter,we take the filter criteria and
330 // create a search term list. Then, we execute the search. We are a search
331 // listener so that we can build up the list of search hits. Then, when the
332 // search is done, we will apply the filter action(s) en-masse, so, for example,
333 // if the action is a move, we calls one method to move all the messages to the
334 // destination folder. Or, mark all the messages read. In the case of imap
335 // operations, or imap/local  moves, the action will be asynchronous, so we'll
336 // need to be a url listener as well, and kick off the next filter when the
337 // action completes.
338 class nsMsgFilterAfterTheFact : public nsIUrlListener,
339                                 public nsIMsgSearchNotify,
340                                 public nsIMsgCopyServiceListener {
341  public:
342   nsMsgFilterAfterTheFact(nsIMsgWindow* aMsgWindow,
343                           nsIMsgFilterList* aFilterList,
344                           const nsTArray<RefPtr<nsIMsgFolder>>& aFolderList,
345                           nsIMsgOperationListener* aCallback);
346   NS_DECL_ISUPPORTS
347   NS_DECL_NSIURLLISTENER
348   NS_DECL_NSIMSGSEARCHNOTIFY
349   NS_DECL_NSIMSGCOPYSERVICELISTENER
350 
351   nsresult AdvanceToNextFolder();  // kicks off the process
352  protected:
353   virtual ~nsMsgFilterAfterTheFact();
354   virtual nsresult RunNextFilter();
355   /**
356    * apply filter actions to current search hits
357    */
358   nsresult ApplyFilter();
359   nsresult OnEndExecution();  // do what we have to do to cleanup.
360   bool ContinueExecutionPrompt();
361   nsresult DisplayConfirmationPrompt(nsIMsgWindow* msgWindow,
362                                      const char16_t* confirmString,
363                                      bool* confirmed);
364   nsCOMPtr<nsIMsgWindow> m_msgWindow;
365   nsCOMPtr<nsIMsgFilterList> m_filters;
366   nsTArray<RefPtr<nsIMsgFolder>> m_folders;
367   nsCOMPtr<nsIMsgFolder> m_curFolder;
368   nsCOMPtr<nsIMsgDatabase> m_curFolderDB;
369   nsCOMPtr<nsIMsgFilter> m_curFilter;
370   uint32_t m_curFilterIndex;
371   uint32_t m_curFolderIndex;
372   uint32_t m_numFilters;
373   nsTArray<nsMsgKey> m_searchHits;
374   nsTArray<RefPtr<nsIMsgDBHdr>> m_searchHitHdrs;
375   nsTArray<nsMsgKey> m_stopFiltering;
376   nsCOMPtr<nsIMsgSearchSession> m_searchSession;
377   nsCOMPtr<nsIMsgOperationListener> m_callback;
378   uint32_t m_nextAction;  // next filter action to perform
379   nsresult mFinalResult;  // report of overall success or failure
380   bool mNeedsRelease;     // Did we need to release ourself?
381 };
382 
NS_IMPL_ISUPPORTS(nsMsgFilterAfterTheFact,nsIUrlListener,nsIMsgSearchNotify,nsIMsgCopyServiceListener)383 NS_IMPL_ISUPPORTS(nsMsgFilterAfterTheFact, nsIUrlListener, nsIMsgSearchNotify,
384                   nsIMsgCopyServiceListener)
385 
386 nsMsgFilterAfterTheFact::nsMsgFilterAfterTheFact(
387     nsIMsgWindow* aMsgWindow, nsIMsgFilterList* aFilterList,
388     const nsTArray<RefPtr<nsIMsgFolder>>& aFolderList,
389     nsIMsgOperationListener* aCallback) {
390   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, ("(Post) nsMsgFilterAfterTheFact"));
391   m_curFilterIndex = m_curFolderIndex = m_nextAction = 0;
392   m_msgWindow = aMsgWindow;
393   m_filters = aFilterList;
394   m_folders = aFolderList.Clone();
395   m_filters->GetFilterCount(&m_numFilters);
396 
397   NS_ADDREF_THIS();  // we own ourselves, and will release ourselves when
398                      // execution is done.
399   mNeedsRelease = true;
400 
401   m_callback = aCallback;
402   mFinalResult = NS_OK;
403 }
404 
~nsMsgFilterAfterTheFact()405 nsMsgFilterAfterTheFact::~nsMsgFilterAfterTheFact() {
406   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
407           ("(Post) ~nsMsgFilterAfterTheFact"));
408 }
409 
410 // do what we have to do to cleanup.
OnEndExecution()411 nsresult nsMsgFilterAfterTheFact::OnEndExecution() {
412   if (m_searchSession) m_searchSession->UnregisterListener(this);
413 
414   if (m_filters) (void)m_filters->FlushLogIfNecessary();
415 
416   if (m_callback) (void)m_callback->OnStopOperation(mFinalResult);
417 
418   nsresult rv = mFinalResult;
419   // OnEndExecution() can be called a second time when a rule execution fails
420   // and the user is prompted whether he wants to continue.
421   if (mNeedsRelease) {
422     NS_RELEASE_THIS();  // release ourselves.
423     mNeedsRelease = false;
424   }
425   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Post) End executing filters"));
426   return rv;
427 }
428 
RunNextFilter()429 nsresult nsMsgFilterAfterTheFact::RunNextFilter() {
430   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
431           ("(Post) nsMsgFilterAfterTheFact::RunNextFilter"));
432   nsresult rv = NS_OK;
433   while (true) {
434     m_curFilter = nullptr;
435     if (m_curFilterIndex >= m_numFilters) break;
436 
437     BREAK_IF_FALSE(m_filters, "Missing filters");
438 
439     MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
440             ("(Post) Running filter %" PRIu32, m_curFilterIndex));
441 
442     rv =
443         m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter));
444     CONTINUE_IF_FAILURE(rv, "Could not get filter at index");
445 
446     nsString filterName;
447     m_curFilter->GetFilterName(filterName);
448     // clang-format off
449     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
450             ("(Post) Filter name: %s", NS_ConvertUTF16toUTF8(filterName).get()));
451     // clang-format on
452 
453     nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms;
454     rv = m_curFilter->GetSearchTerms(searchTerms);
455     CONTINUE_IF_FAILURE(rv, "Could not get searchTerms");
456 
457     if (m_searchSession) m_searchSession->UnregisterListener(this);
458     m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
459     BREAK_IF_FAILURE(rv, "Failed to get search session");
460 
461     nsMsgSearchScopeValue searchScope = nsMsgSearchScope::offlineMail;
462     for (nsIMsgSearchTerm* term : searchTerms) {
463       rv = m_searchSession->AppendTerm(term);
464       BREAK_IF_FAILURE(rv, "Could not append search term");
465     }
466     CONTINUE_IF_FAILURE(rv, "Failed to setup search terms");
467     m_searchSession->RegisterListener(this,
468                                       nsIMsgSearchSession::allNotifications);
469 
470     rv = m_searchSession->AddScopeTerm(searchScope, m_curFolder);
471     CONTINUE_IF_FAILURE(rv, "Failed to add scope term");
472     m_nextAction = 0;
473     rv = m_searchSession->Search(m_msgWindow);
474     CONTINUE_IF_FAILURE(rv, "Search failed");
475     return NS_OK;  // OnSearchDone will continue
476   }
477 
478   if (NS_FAILED(rv)) {
479     MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
480             ("(Post) Filter evaluation failed"));
481     m_filters->LogFilterMessage(u"Filter evaluation failed"_ns, m_curFilter);
482   }
483 
484   m_curFilter = nullptr;
485   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Search failed");
486   return AdvanceToNextFolder();
487 }
488 
AdvanceToNextFolder()489 nsresult nsMsgFilterAfterTheFact::AdvanceToNextFolder() {
490   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
491           ("(Post) nsMsgFilterAfterTheFact::AdvanceToNextFolder"));
492   nsresult rv = NS_OK;
493   // Advance through folders, making sure m_curFolder is null on errors
494   while (true) {
495     m_stopFiltering.Clear();
496     m_curFolder = nullptr;
497     if (m_curFolderIndex >= m_folders.Length()) {
498       // final end of nsMsgFilterAfterTheFact object
499       return OnEndExecution();
500     }
501 
502     MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
503             ("(Post) Entering folder %" PRIu32, m_curFolderIndex));
504 
505     // reset the filter index to apply all filters to this new folder
506     m_curFilterIndex = 0;
507     m_nextAction = 0;
508     m_curFolder = m_folders[m_curFolderIndex++];
509 
510     // Note: I got rv = NS_OK but null m_curFolder after deleting a folder
511     // outside of TB, when I select a single message and "run filter on message"
512     // and the filter is to move the message to the deleted folder.
513 
514     // m_curFolder may be null when the folder is deleted externally.
515     CONTINUE_IF_FALSE(m_curFolder, "Next folder returned null");
516 
517     nsString folderName;
518     (void)m_curFolder->GetName(folderName);
519     MOZ_LOG(
520         FILTERLOGMODULE, LogLevel::Info,
521         ("(Post) Folder name: %s", NS_ConvertUTF16toUTF8(folderName).get()));
522 
523     nsCOMPtr<nsIFile> folderPath;
524     (void)m_curFolder->GetFilePath(getter_AddRefs(folderPath));
525     (void)folderPath->GetPath(folderName);
526     MOZ_LOG(
527         FILTERLOGMODULE, LogLevel::Debug,
528         ("(Post) Folder path: %s", NS_ConvertUTF16toUTF8(folderName).get()));
529 
530     rv = m_curFolder->GetMsgDatabase(getter_AddRefs(m_curFolderDB));
531     if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) {
532       nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
533           do_QueryInterface(m_curFolder, &rv);
534       if (NS_SUCCEEDED(rv) && localFolder)
535         // will continue with OnStopRunningUrl
536         return localFolder->ParseFolder(m_msgWindow, this);
537     }
538     CONTINUE_IF_FAILURE(rv, "Could not get folder db");
539 
540     rv = RunNextFilter();
541     // RunNextFilter returns success when either filters are done, or an async
542     // process has started. It will call AdvanceToNextFolder itself if possible,
543     // so no need to call here.
544     BREAK_IF_FAILURE(rv, "Failed to run next filter");
545     break;
546   }
547   return rv;
548 }
549 
OnStartRunningUrl(nsIURI * aUrl)550 NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStartRunningUrl(nsIURI* aUrl) {
551   return NS_OK;
552 }
553 
554 // This is the return from a folder parse
OnStopRunningUrl(nsIURI * aUrl,nsresult aExitCode)555 NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopRunningUrl(nsIURI* aUrl,
556                                                         nsresult aExitCode) {
557   if (NS_SUCCEEDED(aExitCode)) return RunNextFilter();
558 
559   mFinalResult = aExitCode;
560   // If m_msgWindow then we are in a context where the user can deal with
561   //  errors. Put up a prompt, and exit if user wants.
562   if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution();
563 
564   // folder parse failed, so stop processing this folder.
565   return AdvanceToNextFolder();
566 }
567 
OnSearchHit(nsIMsgDBHdr * header,nsIMsgFolder * folder)568 NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchHit(nsIMsgDBHdr* header,
569                                                    nsIMsgFolder* folder) {
570   NS_ENSURE_ARG_POINTER(header);
571 
572   nsMsgKey msgKey;
573   header->GetMessageKey(&msgKey);
574 
575   nsCString msgId;
576   header->GetMessageId(getter_Copies(msgId));
577   // clang-format off
578   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
579           ("(Post) Filter matched message with key %" PRIu32,
580            msgKeyToInt(msgKey)));
581   // clang-format on
582   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
583           ("(Post) Matched message ID: %s", msgId.get()));
584 
585   // Under various previous actions (a move, delete, or stopExecution)
586   //  we do not want to process filters on a per-message basis.
587   if (m_stopFiltering.Contains(msgKey)) {
588     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
589             ("(Post) Stopping further filter execution on this message"));
590     return NS_OK;
591   }
592 
593   m_searchHits.AppendElement(msgKey);
594   m_searchHitHdrs.AppendElement(header);
595   return NS_OK;
596 }
597 
598 // Continue after an async operation.
OnSearchDone(nsresult status)599 NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchDone(nsresult status) {
600   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
601           ("(Post) Done matching current filter"));
602   if (NS_SUCCEEDED(status))
603     return m_searchHits.IsEmpty() ? RunNextFilter() : ApplyFilter();
604 
605   mFinalResult = status;
606   if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution();
607 
608   // The search failed, so move on to the next filter.
609   return RunNextFilter();
610 }
611 
OnNewSearch()612 NS_IMETHODIMP nsMsgFilterAfterTheFact::OnNewSearch() {
613   m_searchHits.Clear();
614   m_searchHitHdrs.Clear();
615   return NS_OK;
616 }
617 
618 // This method will apply filters. It will continue to advance though headers,
619 //   filters, and folders until done, unless it starts an async operation with
620 //   a callback. The callback should call ApplyFilter again. It only returns
621 //   an error if it is impossible to continue after attempting to continue the
622 //   next filter action, filter, or folder.
ApplyFilter()623 nsresult nsMsgFilterAfterTheFact::ApplyFilter() {
624   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
625           ("(Post) nsMsgFilterAfterTheFact::ApplyFilter"));
626   nsresult rv;
627   do {
628     // Error management block, break if unable to continue with filter.
629 
630     if (!m_curFilter)
631       break;  // Maybe not an error, we just need to call RunNextFilter();
632     if (!m_curFolder)
633       break;  // Maybe not an error, we just need to call AdvanceToNextFolder();
634 
635     // 'm_curFolder' can be reset asynchronously by the copy service
636     // calling OnStopCopy(). So take a local copy here and use it throughout the
637     // function.
638     nsCOMPtr<nsIMsgFolder> curFolder = m_curFolder;
639     nsCOMPtr<nsIMsgFilter> curFilter = m_curFilter;
640 
641     // We're going to log the filter actions before firing them because some
642     // actions are async.
643     bool loggingEnabled = false;
644     if (m_filters) (void)m_filters->GetLoggingEnabled(&loggingEnabled);
645 
646     nsTArray<RefPtr<nsIMsgRuleAction>> actionList;
647     rv = curFilter->GetSortedActionList(actionList);
648     BREAK_IF_FAILURE(rv, "Could not get action list for filter");
649 
650     uint32_t numActions = actionList.Length();
651 
652     if (m_nextAction == 0) {
653       MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
654               ("(Post) Applying %" PRIu32 " filter actions to %" PRIu32
655                " matched messages",
656                numActions, static_cast<uint32_t>(m_searchHits.Length())));
657     } else if (m_nextAction < numActions) {
658       MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
659               ("(Post) Applying remaining %" PRIu32
660                " filter actions to %" PRIu32 " matched messages",
661                numActions - m_nextAction,
662                static_cast<uint32_t>(m_searchHits.Length())));
663     }
664 
665     // We start from m_nextAction to allow us to continue applying actions
666     // after the return from an async copy.
667     while (m_nextAction < numActions) {
668       nsresult finalResult = NS_OK;
669       nsCOMPtr<nsIMsgRuleAction> filterAction(actionList[m_nextAction]);
670       ++m_nextAction;
671 
672       nsMsgRuleActionType actionType;
673       rv = filterAction->GetType(&actionType);
674       CONTINUE_IF_FAILURE(rv, "Could not get type for filter action");
675       MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
676               ("(Post) Running filter action at index %" PRIu32
677                ", action type = %i",
678                m_nextAction - 1, actionType));
679 
680       nsCString actionTargetFolderUri;
681       if (actionType == nsMsgFilterAction::MoveToFolder ||
682           actionType == nsMsgFilterAction::CopyToFolder) {
683         rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
684         CONTINUE_IF_FAILURE(rv, "GetTargetFolderUri failed");
685         CONTINUE_IF_FALSE(!actionTargetFolderUri.IsEmpty(),
686                           "actionTargetFolderUri is empty");
687       }
688 
689       if (loggingEnabled) {
690         for (auto msgHdr : m_searchHitHdrs) {
691           (void)curFilter->LogRuleHit(filterAction, msgHdr);
692         }
693       }
694 
695       // all actions that pass "this" as a listener in order to chain filter
696       // execution when the action is finished need to return before reaching
697       // the bottom of this routine, because we run the next filter at the end
698       // of this routine.
699       switch (actionType) {
700         case nsMsgFilterAction::Delete:
701           // we can't pass ourselves in as a copy service listener because the
702           // copy service listener won't get called in several situations (e.g.,
703           // the delete model is imap delete) and we rely on the listener
704           // getting called to continue the filter application. This means we're
705           // going to end up firing off the delete, and then subsequently
706           // issuing a search for the next filter, which will block until the
707           // delete finishes.
708           rv = curFolder->DeleteMessages(m_searchHitHdrs, m_msgWindow, false,
709                                          false, nullptr, false /*allow Undo*/);
710           BREAK_ACTION_IF_FAILURE(rv, "Deleting messages failed");
711 
712           // don't allow any more filters on this message
713           m_stopFiltering.AppendElements(m_searchHits);
714           for (uint32_t i = 0; i < m_searchHits.Length(); i++)
715             curFolder->OrProcessingFlags(m_searchHits[i],
716                                          nsMsgProcessingFlags::FilterToMove);
717           // if we are deleting then we couldn't care less about applying
718           // remaining filter actions
719           m_nextAction = numActions;
720           break;
721 
722         case nsMsgFilterAction::MoveToFolder:
723           // Even if move fails we will not run additional actions, as they
724           // would not have run if move succeeded.
725           m_nextAction = numActions;
726           // Fall through to the copy case.
727           [[fallthrough]];
728         case nsMsgFilterAction::CopyToFolder: {
729           nsCString uri;
730           curFolder->GetURI(uri);
731 
732           if (uri.Equals(actionTargetFolderUri)) {
733             MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
734                     ("(Post) Target folder is the same as source folder, "
735                      "skipping"));
736             break;
737           }
738 
739           nsCOMPtr<nsIMsgFolder> destIFolder;
740           rv = GetOrCreateFolder(actionTargetFolderUri,
741                                  getter_AddRefs(destIFolder));
742           BREAK_ACTION_IF_FAILURE(rv, "Could not get action folder");
743 
744           bool canFileMessages = true;
745           nsCOMPtr<nsIMsgFolder> parentFolder;
746           destIFolder->GetParent(getter_AddRefs(parentFolder));
747           if (parentFolder) destIFolder->GetCanFileMessages(&canFileMessages);
748           if (!parentFolder || !canFileMessages) {
749             curFilter->SetEnabled(false);
750             destIFolder->ThrowAlertMsg("filterDisabled", m_msgWindow);
751             // we need to explicitly save the filter file.
752             m_filters->SaveToDefaultFile();
753             // In the case of applying multiple filters
754             // we might want to remove the filter from the list, but
755             // that's a bit evil since we really don't know that we own
756             // the list. Disabling it doesn't do a lot of good since
757             // we still apply disabled filters. Currently, we don't
758             // have any clients that apply filters to multiple folders,
759             // so this might be the edge case of an edge case.
760             m_nextAction = numActions;
761             BREAK_ACTION_IF_FALSE(false,
762                                   "No parent folder or folder can't file "
763                                   "messages, disabling the filter");
764           }
765           nsCOMPtr<nsIMsgCopyService> copyService =
766               do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
767           BREAK_ACTION_IF_FAILURE(rv, "Could not get copy service");
768 
769           if (actionType == nsMsgFilterAction::MoveToFolder) {
770             m_stopFiltering.AppendElements(m_searchHits);
771             for (uint32_t i = 0; i < m_searchHits.Length(); i++)
772               curFolder->OrProcessingFlags(m_searchHits[i],
773                                            nsMsgProcessingFlags::FilterToMove);
774           }
775 
776           rv = copyService->CopyMessages(
777               curFolder, m_searchHitHdrs, destIFolder,
778               actionType == nsMsgFilterAction::MoveToFolder, this, m_msgWindow,
779               false);
780           BREAK_ACTION_IF_FAILURE(rv, "CopyMessages failed");
781           MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
782                   ("(Post) Action execution continues async"));
783           return NS_OK;  // OnStopCopy callback to continue;
784         } break;
785         case nsMsgFilterAction::MarkRead:
786           // crud, no listener support here - we'll probably just need to go on
787           // and apply the next filter, and, in the imap case, rely on multiple
788           // connection and url queueing to stay out of trouble
789           rv = curFolder->MarkMessagesRead(m_searchHitHdrs, true);
790           BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
791           break;
792         case nsMsgFilterAction::MarkUnread:
793           rv = curFolder->MarkMessagesRead(m_searchHitHdrs, false);
794           BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
795           break;
796         case nsMsgFilterAction::MarkFlagged:
797           rv = curFolder->MarkMessagesFlagged(m_searchHitHdrs, true);
798           BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
799           break;
800         case nsMsgFilterAction::KillThread:
801         case nsMsgFilterAction::WatchThread: {
802           for (auto msgHdr : m_searchHitHdrs) {
803             nsCOMPtr<nsIMsgThread> msgThread;
804             nsMsgKey threadKey;
805             m_curFolderDB->GetThreadContainingMsgHdr(msgHdr,
806                                                      getter_AddRefs(msgThread));
807             BREAK_ACTION_IF_FALSE(msgThread, "Could not find msg thread");
808             msgThread->GetThreadKey(&threadKey);
809             if (actionType == nsMsgFilterAction::KillThread) {
810               rv = m_curFolderDB->MarkThreadIgnored(msgThread, threadKey, true,
811                                                     nullptr);
812               BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
813             } else {
814               rv = m_curFolderDB->MarkThreadWatched(msgThread, threadKey, true,
815                                                     nullptr);
816               BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
817             }
818           }
819         } break;
820         case nsMsgFilterAction::KillSubthread: {
821           for (auto msgHdr : m_searchHitHdrs) {
822             rv = m_curFolderDB->MarkHeaderKilled(msgHdr, true, nullptr);
823             BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
824           }
825         } break;
826         case nsMsgFilterAction::ChangePriority: {
827           nsMsgPriorityValue filterPriority;
828           filterAction->GetPriority(&filterPriority);
829           for (auto msgHdr : m_searchHitHdrs) {
830             rv = msgHdr->SetPriority(filterPriority);
831             BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
832           }
833         } break;
834         case nsMsgFilterAction::Label: {
835           nsMsgLabelValue filterLabel;
836           filterAction->GetLabel(&filterLabel);
837           rv = curFolder->SetLabelForMessages(m_searchHitHdrs, filterLabel);
838           BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
839         } break;
840         case nsMsgFilterAction::AddTag: {
841           nsCString keyword;
842           filterAction->GetStrValue(keyword);
843           rv = curFolder->AddKeywordsToMessages(m_searchHitHdrs, keyword);
844           BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
845         } break;
846         case nsMsgFilterAction::JunkScore: {
847           nsAutoCString junkScoreStr;
848           int32_t junkScore;
849           filterAction->GetJunkScore(&junkScore);
850           junkScoreStr.AppendInt(junkScore);
851           rv =
852               curFolder->SetJunkScoreForMessages(m_searchHitHdrs, junkScoreStr);
853           BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
854         } break;
855         case nsMsgFilterAction::Forward: {
856           nsCOMPtr<nsIMsgIncomingServer> server;
857           rv = curFolder->GetServer(getter_AddRefs(server));
858           BREAK_ACTION_IF_FAILURE(rv, "Could not get server");
859           nsCString forwardTo;
860           filterAction->GetStrValue(forwardTo);
861           BREAK_ACTION_IF_FALSE(!forwardTo.IsEmpty(), "blank forwardTo URI");
862           nsCOMPtr<nsIMsgComposeService> compService =
863               do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
864           BREAK_ACTION_IF_FAILURE(rv, "Could not get compose service");
865 
866           for (auto msgHdr : m_searchHitHdrs) {
867             rv = compService->ForwardMessage(
868                 NS_ConvertASCIItoUTF16(forwardTo), msgHdr, m_msgWindow, server,
869                 nsIMsgComposeService::kForwardAsDefault);
870             BREAK_ACTION_IF_FAILURE(rv, "Forward action failed");
871           }
872         } break;
873         case nsMsgFilterAction::Reply: {
874           nsCString replyTemplateUri;
875           filterAction->GetStrValue(replyTemplateUri);
876           BREAK_ACTION_IF_FALSE(!replyTemplateUri.IsEmpty(),
877                                 "Empty reply template URI");
878 
879           nsCOMPtr<nsIMsgIncomingServer> server;
880           rv = curFolder->GetServer(getter_AddRefs(server));
881           BREAK_ACTION_IF_FAILURE(rv, "Could not get server");
882 
883           nsCOMPtr<nsIMsgComposeService> compService =
884               do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
885           BREAK_ACTION_IF_FAILURE(rv, "Could not get compose service");
886           for (auto msgHdr : m_searchHitHdrs) {
887             rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri,
888                                                 m_msgWindow, server);
889             if (NS_FAILED(rv)) {
890               if (rv == NS_ERROR_ABORT) {
891                 (void)curFilter->LogRuleHitFail(
892                     filterAction, msgHdr, rv,
893                     "filterFailureSendingReplyAborted"_ns);
894               } else {
895                 (void)curFilter->LogRuleHitFail(
896                     filterAction, msgHdr, rv,
897                     "filterFailureSendingReplyError"_ns);
898               }
899             }
900             BREAK_ACTION_IF_FAILURE(rv, "ReplyWithTemplate failed");
901           }
902         } break;
903         case nsMsgFilterAction::DeleteFromPop3Server: {
904           nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
905               do_QueryInterface(curFolder, &rv);
906           BREAK_ACTION_IF_FAILURE(rv, "Current folder not a local folder");
907           BREAK_ACTION_IF_FALSE(localFolder,
908                                 "Current folder not a local folder");
909           // This action ignores the deleteMailLeftOnServer preference
910           rv = localFolder->MarkMsgsOnPop3Server(m_searchHitHdrs,
911                                                  POP3_FORCE_DEL);
912           BREAK_ACTION_IF_FAILURE(rv, "MarkMsgsOnPop3Server failed");
913 
914           // Delete the partial headers. They're useless now
915           //   that the server copy is being deleted.
916           nsTArray<RefPtr<nsIMsgDBHdr>> partialMsgs;
917           for (uint32_t i = 0; i < m_searchHits.Length(); ++i) {
918             nsIMsgDBHdr* msgHdr = m_searchHitHdrs[i];
919             nsMsgKey msgKey = m_searchHits[i];
920             uint32_t flags;
921             msgHdr->GetFlags(&flags);
922             if (flags & nsMsgMessageFlags::Partial) {
923               partialMsgs.AppendElement(msgHdr);
924               m_stopFiltering.AppendElement(msgKey);
925               curFolder->OrProcessingFlags(msgKey,
926                                            nsMsgProcessingFlags::FilterToMove);
927             }
928           }
929           if (!partialMsgs.IsEmpty()) {
930             rv = curFolder->DeleteMessages(partialMsgs, m_msgWindow, true,
931                                            false, nullptr, false);
932             BREAK_ACTION_IF_FAILURE(rv, "Delete messages failed");
933           }
934         } break;
935         case nsMsgFilterAction::FetchBodyFromPop3Server: {
936           nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
937               do_QueryInterface(curFolder, &rv);
938           BREAK_ACTION_IF_FAILURE(rv, "current folder not local");
939           BREAK_ACTION_IF_FALSE(localFolder, "current folder not local");
940           nsTArray<RefPtr<nsIMsgDBHdr>> messages;
941           for (nsIMsgDBHdr* msgHdr : m_searchHitHdrs) {
942             uint32_t flags = 0;
943             msgHdr->GetFlags(&flags);
944             if (flags & nsMsgMessageFlags::Partial)
945               messages.AppendElement(msgHdr);
946           }
947           if (messages.Length() > 0) {
948             rv = curFolder->DownloadMessagesForOffline(messages, m_msgWindow);
949             BREAK_ACTION_IF_FAILURE(rv, "DownloadMessagesForOffline failed");
950           }
951         } break;
952 
953         case nsMsgFilterAction::StopExecution: {
954           // don't apply any more filters
955           m_stopFiltering.AppendElements(m_searchHits);
956           m_nextAction = numActions;
957         } break;
958 
959         case nsMsgFilterAction::Custom: {
960           nsMsgFilterTypeType filterType;
961           curFilter->GetFilterType(&filterType);
962           nsCOMPtr<nsIMsgFilterCustomAction> customAction;
963           rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
964           BREAK_ACTION_IF_FAILURE(rv, "Could not get custom action");
965 
966           nsAutoCString value;
967           rv = filterAction->GetStrValue(value);
968           BREAK_ACTION_IF_FAILURE(rv, "Could not get custom action value");
969           bool isAsync = false;
970           customAction->GetIsAsync(&isAsync);
971           rv = customAction->ApplyAction(m_searchHitHdrs, value, this,
972                                          filterType, m_msgWindow);
973           BREAK_ACTION_IF_FAILURE(rv, "custom action failed to apply");
974           if (isAsync) {
975             MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
976                     ("(Post) Action execution continues async"));
977             return NS_OK;  // custom action should call ApplyFilter on callback
978           }
979         } break;
980 
981         default:
982           NS_ERROR("unexpected filter action");
983           BREAK_ACTION_IF_FAILURE(NS_ERROR_UNEXPECTED,
984                                   "Unexpected filter action");
985       }
986       if (NS_FAILED(finalResult)) {
987         mFinalResult = finalResult;
988         MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
989                 ("(Post) Action execution failed with error: %" PRIx32,
990                  static_cast<uint32_t>(mFinalResult)));
991         if (loggingEnabled && m_searchHitHdrs.Length() > 0) {
992           (void)curFilter->LogRuleHitFail(filterAction, m_searchHitHdrs[0],
993                                           mFinalResult,
994                                           "filterActionFailed"_ns);
995         }
996       } else {
997         MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
998                 ("(Post) Action execution succeeded"));
999       }
1000     }
1001   } while (false);  // end error management block
1002   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1003           ("(Post) Finished executing actions"));
1004   return RunNextFilter();
1005 }
1006 
GetTempFilterList(nsIMsgFolder * aFolder,nsIMsgFilterList ** aFilterList)1007 NS_IMETHODIMP nsMsgFilterService::GetTempFilterList(
1008     nsIMsgFolder* aFolder, nsIMsgFilterList** aFilterList) {
1009   NS_ENSURE_ARG_POINTER(aFilterList);
1010 
1011   nsMsgFilterList* filterList = new nsMsgFilterList;
1012   filterList->SetFolder(aFolder);
1013   filterList->m_temporaryList = true;
1014   NS_ADDREF(*aFilterList = filterList);
1015   return NS_OK;
1016 }
1017 
1018 NS_IMETHODIMP
ApplyFiltersToFolders(nsIMsgFilterList * aFilterList,const nsTArray<RefPtr<nsIMsgFolder>> & aFolders,nsIMsgWindow * aMsgWindow,nsIMsgOperationListener * aCallback)1019 nsMsgFilterService::ApplyFiltersToFolders(
1020     nsIMsgFilterList* aFilterList,
1021     const nsTArray<RefPtr<nsIMsgFolder>>& aFolders, nsIMsgWindow* aMsgWindow,
1022     nsIMsgOperationListener* aCallback) {
1023   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
1024           ("(Post) nsMsgFilterService::ApplyFiltersToFolders"));
1025   NS_ENSURE_ARG_POINTER(aFilterList);
1026 
1027   uint32_t filterCount;
1028   aFilterList->GetFilterCount(&filterCount);
1029   nsCString listId;
1030   aFilterList->GetListId(listId);
1031   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1032           ("(Post) Manual filter run initiated"));
1033   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1034           ("(Post) Running %" PRIu32 " filters from %s on %" PRIu32 " folders",
1035            filterCount, listId.get(), (int)aFolders.Length()));
1036 
1037   RefPtr<nsMsgFilterAfterTheFact> filterExecutor =
1038       new nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolders, aCallback);
1039   if (filterExecutor)
1040     return filterExecutor->AdvanceToNextFolder();
1041   else
1042     return NS_ERROR_OUT_OF_MEMORY;
1043 }
1044 
AddCustomAction(nsIMsgFilterCustomAction * aAction)1045 NS_IMETHODIMP nsMsgFilterService::AddCustomAction(
1046     nsIMsgFilterCustomAction* aAction) {
1047   mCustomActions.AppendElement(aAction);
1048   return NS_OK;
1049 }
1050 
GetCustomActions(nsTArray<RefPtr<nsIMsgFilterCustomAction>> & actions)1051 NS_IMETHODIMP nsMsgFilterService::GetCustomActions(
1052     nsTArray<RefPtr<nsIMsgFilterCustomAction>>& actions) {
1053   actions = mCustomActions.Clone();
1054   return NS_OK;
1055 }
1056 
1057 NS_IMETHODIMP
GetCustomAction(const nsACString & aId,nsIMsgFilterCustomAction ** aResult)1058 nsMsgFilterService::GetCustomAction(const nsACString& aId,
1059                                     nsIMsgFilterCustomAction** aResult) {
1060   NS_ENSURE_ARG_POINTER(aResult);
1061 
1062   for (nsIMsgFilterCustomAction* action : mCustomActions) {
1063     nsAutoCString id;
1064     nsresult rv = action->GetId(id);
1065     if (NS_SUCCEEDED(rv) && aId.Equals(id)) {
1066       NS_ADDREF(*aResult = action);
1067       return NS_OK;
1068     }
1069   }
1070   aResult = nullptr;
1071   return NS_ERROR_FAILURE;
1072 }
1073 
AddCustomTerm(nsIMsgSearchCustomTerm * aTerm)1074 NS_IMETHODIMP nsMsgFilterService::AddCustomTerm(nsIMsgSearchCustomTerm* aTerm) {
1075   mCustomTerms.AppendElement(aTerm);
1076   return NS_OK;
1077 }
1078 
GetCustomTerms(nsTArray<RefPtr<nsIMsgSearchCustomTerm>> & terms)1079 NS_IMETHODIMP nsMsgFilterService::GetCustomTerms(
1080     nsTArray<RefPtr<nsIMsgSearchCustomTerm>>& terms) {
1081   terms = mCustomTerms.Clone();
1082   return NS_OK;
1083 }
1084 
1085 NS_IMETHODIMP
GetCustomTerm(const nsACString & aId,nsIMsgSearchCustomTerm ** aResult)1086 nsMsgFilterService::GetCustomTerm(const nsACString& aId,
1087                                   nsIMsgSearchCustomTerm** aResult) {
1088   NS_ENSURE_ARG_POINTER(aResult);
1089 
1090   for (nsIMsgSearchCustomTerm* term : mCustomTerms) {
1091     nsAutoCString id;
1092     nsresult rv = term->GetId(id);
1093     if (NS_SUCCEEDED(rv) && aId.Equals(id)) {
1094       NS_ADDREF(*aResult = term);
1095       return NS_OK;
1096     }
1097   }
1098   aResult = nullptr;
1099   // we use a null result to indicate failure to find a term
1100   return NS_OK;
1101 }
1102 
1103 /**
1104  * Translate the filter type flag into human readable type names.
1105  * In case of multiple flag they are delimited by '&'.
1106  */
1107 NS_IMETHODIMP
FilterTypeName(nsMsgFilterTypeType filterType,nsACString & typeName)1108 nsMsgFilterService::FilterTypeName(nsMsgFilterTypeType filterType,
1109                                    nsACString& typeName) {
1110   typeName.Truncate();
1111   if (filterType == nsMsgFilterType::None) {
1112     typeName.Assign("None");
1113     return NS_OK;
1114   }
1115 
1116   if ((filterType & nsMsgFilterType::Incoming) == nsMsgFilterType::Incoming) {
1117     typeName.Append("Incoming&");
1118   } else {
1119     if ((filterType & nsMsgFilterType::Inbox) == nsMsgFilterType::Inbox) {
1120       typeName.Append("Inbox&");
1121     } else {
1122       if (filterType & nsMsgFilterType::InboxRule)
1123         typeName.Append("InboxRule&");
1124       if (filterType & nsMsgFilterType::InboxJavaScript)
1125         typeName.Append("InboxJavaScript&");
1126     }
1127     if ((filterType & nsMsgFilterType::News) == nsMsgFilterType::News) {
1128       typeName.Append("News&");
1129     } else {
1130       if (filterType & nsMsgFilterType::NewsRule) typeName.Append("NewsRule&");
1131       if (filterType & nsMsgFilterType::NewsJavaScript)
1132         typeName.Append("NewsJavaScript&");
1133     }
1134   }
1135   if (filterType & nsMsgFilterType::Manual) typeName.Append("Manual&");
1136   if (filterType & nsMsgFilterType::PostPlugin) typeName.Append("PostPlugin&");
1137   if (filterType & nsMsgFilterType::PostOutgoing)
1138     typeName.Append("PostOutgoing&");
1139   if (filterType & nsMsgFilterType::Archive) typeName.Append("Archive&");
1140   if (filterType & nsMsgFilterType::Periodic) typeName.Append("Periodic&");
1141 
1142   if (typeName.IsEmpty()) {
1143     typeName.Assign("UNKNOWN");
1144   } else {
1145     // Cut the trailing '&' character.
1146     typeName.Truncate(typeName.Length() - 1);
1147   }
1148   return NS_OK;
1149 }
1150 
1151 // nsMsgApplyFiltersToMessages overrides nsMsgFilterAfterTheFact in order to
1152 // apply filters to a list of messages, rather than an entire folder
1153 class nsMsgApplyFiltersToMessages : public nsMsgFilterAfterTheFact {
1154  public:
1155   nsMsgApplyFiltersToMessages(nsIMsgWindow* aMsgWindow,
1156                               nsIMsgFilterList* aFilterList,
1157                               const nsTArray<RefPtr<nsIMsgFolder>>& aFolderList,
1158                               const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgHdrList,
1159                               nsMsgFilterTypeType aFilterType,
1160                               nsIMsgOperationListener* aCallback);
1161 
1162  protected:
1163   virtual nsresult RunNextFilter();
1164 
1165   nsTArray<RefPtr<nsIMsgDBHdr>> m_msgHdrList;
1166   nsMsgFilterTypeType m_filterType;
1167 };
1168 
nsMsgApplyFiltersToMessages(nsIMsgWindow * aMsgWindow,nsIMsgFilterList * aFilterList,const nsTArray<RefPtr<nsIMsgFolder>> & aFolderList,const nsTArray<RefPtr<nsIMsgDBHdr>> & aMsgHdrList,nsMsgFilterTypeType aFilterType,nsIMsgOperationListener * aCallback)1169 nsMsgApplyFiltersToMessages::nsMsgApplyFiltersToMessages(
1170     nsIMsgWindow* aMsgWindow, nsIMsgFilterList* aFilterList,
1171     const nsTArray<RefPtr<nsIMsgFolder>>& aFolderList,
1172     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgHdrList,
1173     nsMsgFilterTypeType aFilterType, nsIMsgOperationListener* aCallback)
1174     : nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolderList, aCallback),
1175       m_msgHdrList(aMsgHdrList.Clone()),
1176       m_filterType(aFilterType) {
1177   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
1178           ("(Post) nsMsgApplyFiltersToMessages"));
1179 }
1180 
RunNextFilter()1181 nsresult nsMsgApplyFiltersToMessages::RunNextFilter() {
1182   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
1183           ("(Post) nsMsgApplyFiltersToMessages::RunNextFilter"));
1184   nsresult rv = NS_OK;
1185   while (true) {
1186     m_curFilter = nullptr;  // we are done with the current filter
1187     if (!m_curFolder ||     // Not an error, we just need to run
1188                             // AdvanceToNextFolder()
1189         m_curFilterIndex >= m_numFilters)
1190       break;
1191 
1192     BREAK_IF_FALSE(m_filters, "No filters");
1193     nsMsgFilterTypeType filterType;
1194     bool isEnabled;
1195     rv =
1196         m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter));
1197     CONTINUE_IF_FAILURE(rv, "Could not get filter");
1198     rv = m_curFilter->GetFilterType(&filterType);
1199     CONTINUE_IF_FAILURE(rv, "Could not get filter type");
1200     if (!(filterType & m_filterType)) continue;
1201     rv = m_curFilter->GetEnabled(&isEnabled);
1202     CONTINUE_IF_FAILURE(rv, "Could not get isEnabled");
1203     if (!isEnabled) continue;
1204 
1205     MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
1206             ("(Post) Running filter %" PRIu32, m_curFilterIndex));
1207     nsString filterName;
1208     m_curFilter->GetFilterName(filterName);
1209     // clang-format off
1210     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1211             ("(Post) Filter name: %s", NS_ConvertUTF16toUTF8(filterName).get()));
1212     // clang-format on
1213 
1214     nsCOMPtr<nsIMsgSearchScopeTerm> scope(new nsMsgSearchScopeTerm(
1215         nullptr, nsMsgSearchScope::offlineMail, m_curFolder));
1216     BREAK_IF_FALSE(scope, "Could not create scope, OOM?");
1217     m_curFilter->SetScope(scope);
1218     OnNewSearch();
1219 
1220     for (auto msgHdr : m_msgHdrList) {
1221       bool matched;
1222       rv = m_curFilter->MatchHdr(msgHdr, m_curFolder, m_curFolderDB,
1223                                  EmptyCString(), &matched);
1224       if (NS_SUCCEEDED(rv) && matched) {
1225         // In order to work with nsMsgFilterAfterTheFact::ApplyFilter we
1226         // initialize nsMsgFilterAfterTheFact's information with a search hit
1227         // now for the message that we're filtering.
1228         OnSearchHit(msgHdr, m_curFolder);
1229       }
1230     }
1231     m_curFilter->SetScope(nullptr);
1232 
1233     if (m_searchHits.Length() > 0) {
1234       m_nextAction = 0;
1235       rv = ApplyFilter();
1236       if (NS_SUCCEEDED(rv))
1237         return NS_OK;  // async callback will continue, or we are done.
1238     }
1239   }
1240 
1241   if (NS_FAILED(rv)) {
1242     // clang-format off
1243     MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
1244             ("(Post) Filter run failed (%" PRIx32 ")",
1245              static_cast<uint32_t>(rv)));
1246     // clang-format on
1247     m_filters->LogFilterMessage(u"Filter run failed"_ns, m_curFilter);
1248     NS_WARNING_ASSERTION(false, "Failed to run filters");
1249   } else {
1250     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1251             ("(Post) Filter run finished on the current folder"));
1252   }
1253 
1254   m_curFilter = nullptr;
1255 
1256   // We expect the failure is already recorded through one of the macro
1257   // expressions, that will have console logging added to them.
1258   // So an additional console warning is not needed here.
1259   return AdvanceToNextFolder();
1260 }
1261 
ApplyFilters(nsMsgFilterTypeType aFilterType,const nsTArray<RefPtr<nsIMsgDBHdr>> & aMsgHdrList,nsIMsgFolder * aFolder,nsIMsgWindow * aMsgWindow,nsIMsgOperationListener * aCallback)1262 NS_IMETHODIMP nsMsgFilterService::ApplyFilters(
1263     nsMsgFilterTypeType aFilterType,
1264     const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgHdrList, nsIMsgFolder* aFolder,
1265     nsIMsgWindow* aMsgWindow, nsIMsgOperationListener* aCallback) {
1266   MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
1267           ("(Post) nsMsgApplyFiltersToMessages::ApplyFilters"));
1268   NS_ENSURE_ARG_POINTER(aFolder);
1269 
1270   nsCOMPtr<nsIMsgFilterList> filterList;
1271   nsresult rv = aFolder->GetFilterList(aMsgWindow, getter_AddRefs(filterList));
1272   NS_ENSURE_SUCCESS(rv, rv);
1273 
1274   uint32_t filterCount;
1275   filterList->GetFilterCount(&filterCount);
1276   nsCString listId;
1277   filterList->GetListId(listId);
1278   nsString folderName;
1279   aFolder->GetName(folderName);
1280   nsCString typeName;
1281   FilterTypeName(aFilterType, typeName);
1282   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1283           ("(Post) Filter run initiated, trigger=%s (%i)", typeName.get(),
1284            aFilterType));
1285   MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1286           ("(Post) Running %" PRIu32 " filters from %s on %" PRIu32
1287            " message(s) in folder '%s'",
1288            filterCount, listId.get(), (uint32_t)aMsgHdrList.Length(),
1289            NS_ConvertUTF16toUTF8(folderName).get()));
1290 
1291   // Create our nsMsgApplyFiltersToMessages object which will be called when
1292   // ApplyFiltersToHdr finds one or more filters that hit.
1293   RefPtr<nsMsgApplyFiltersToMessages> filterExecutor =
1294       new nsMsgApplyFiltersToMessages(aMsgWindow, filterList, {aFolder},
1295                                       aMsgHdrList, aFilterType, aCallback);
1296 
1297   if (filterExecutor) return filterExecutor->AdvanceToNextFolder();
1298 
1299   return NS_ERROR_OUT_OF_MEMORY;
1300 }
1301 
1302 /* void OnStartCopy (); */
OnStartCopy()1303 NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStartCopy() { return NS_OK; }
1304 
1305 /* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
OnProgress(uint32_t aProgress,uint32_t aProgressMax)1306 NS_IMETHODIMP nsMsgFilterAfterTheFact::OnProgress(uint32_t aProgress,
1307                                                   uint32_t aProgressMax) {
1308   return NS_OK;
1309 }
1310 
1311 /* void SetMessageKey (in uint32_t aKey); */
SetMessageKey(nsMsgKey)1312 NS_IMETHODIMP nsMsgFilterAfterTheFact::SetMessageKey(nsMsgKey /* aKey */) {
1313   return NS_OK;
1314 }
1315 
GetMessageId(nsACString & messageId)1316 NS_IMETHODIMP nsMsgFilterAfterTheFact::GetMessageId(nsACString& messageId) {
1317   return NS_OK;
1318 }
1319 
1320 /* void OnStopCopy (in nsresult aStatus); */
OnStopCopy(nsresult aStatus)1321 NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopCopy(nsresult aStatus) {
1322   if (NS_SUCCEEDED(aStatus)) {
1323     // clang-format off
1324     MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1325             ("(Post) Async message copy from filter action finished successfully"));
1326     // clang-format on
1327     return ApplyFilter();
1328   }
1329   MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
1330           ("(Post) Async message copy from filter action failed (%" PRIx32 ")",
1331            static_cast<uint32_t>(aStatus)));
1332 
1333   mFinalResult = aStatus;
1334   if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution();
1335 
1336   // Copy failed, so run the next filter
1337   return RunNextFilter();
1338 }
1339 
ContinueExecutionPrompt()1340 bool nsMsgFilterAfterTheFact::ContinueExecutionPrompt() {
1341   if (!m_curFilter) return false;
1342   nsCOMPtr<nsIStringBundle> bundle;
1343   nsCOMPtr<nsIStringBundleService> bundleService =
1344       mozilla::services::GetStringBundleService();
1345   if (!bundleService) return false;
1346   bundleService->CreateBundle("chrome://messenger/locale/filter.properties",
1347                               getter_AddRefs(bundle));
1348   if (!bundle) return false;
1349   nsString filterName;
1350   m_curFilter->GetFilterName(filterName);
1351   nsString formatString;
1352   nsString confirmText;
1353   AutoTArray<nsString, 1> formatStrings = {filterName};
1354   nsresult rv = bundle->FormatStringFromName("continueFilterExecution",
1355                                              formatStrings, confirmText);
1356   if (NS_FAILED(rv)) return false;
1357   bool returnVal = false;
1358   (void)DisplayConfirmationPrompt(m_msgWindow, confirmText.get(), &returnVal);
1359   if (!returnVal) {
1360     MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
1361             ("(Post) User aborted further filter execution on prompt"));
1362   }
1363   return returnVal;
1364 }
1365 
DisplayConfirmationPrompt(nsIMsgWindow * msgWindow,const char16_t * confirmString,bool * confirmed)1366 nsresult nsMsgFilterAfterTheFact::DisplayConfirmationPrompt(
1367     nsIMsgWindow* msgWindow, const char16_t* confirmString, bool* confirmed) {
1368   if (msgWindow) {
1369     nsCOMPtr<nsIDocShell> docShell;
1370     msgWindow->GetRootDocShell(getter_AddRefs(docShell));
1371     if (docShell) {
1372       nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
1373       if (dialog && confirmString)
1374         dialog->Confirm(nullptr, confirmString, confirmed);
1375     }
1376   }
1377   return NS_OK;
1378 }
1379