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