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 nsMsgFilterList interface
7
8 #include "nsTextFormatter.h"
9
10 #include "msgCore.h"
11 #include "nsMsgFilterList.h"
12 #include "nsMsgFilter.h"
13 #include "nsIMsgHdr.h"
14 #include "nsIMsgFilterHitNotify.h"
15 #include "nsMsgUtils.h"
16 #include "nsMsgSearchTerm.h"
17 #include "nsString.h"
18 #include "nsMsgBaseCID.h"
19 #include "nsIMsgFilterService.h"
20 #include "nsMsgSearchScopeTerm.h"
21 #include "nsIStringBundle.h"
22 #include "nsNetUtil.h"
23 #include "nsIInputStream.h"
24 #include "nsNativeCharsetUtils.h"
25 #include "nsMemory.h"
26 #include "prmem.h"
27 #include "mozilla/ArrayUtils.h"
28 #include "mozilla/Logging.h"
29 #include <ctype.h>
30
31 // Marker for EOF or failure during read
32 #define EOF_CHAR -1
33
34 using namespace mozilla;
35
36 extern LazyLogModule FILTERLOGMODULE;
37
38 static uint32_t nextListId = 0;
39
nsMsgFilterList()40 nsMsgFilterList::nsMsgFilterList() : m_fileVersion(0) {
41 m_loggingEnabled = false;
42 m_startWritingToBuffer = false;
43 m_temporaryList = false;
44 m_curFilter = nullptr;
45 m_listId.Assign("List");
46 m_listId.AppendInt(nextListId++);
47 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
48 ("Creating a new filter list with id=%s", m_listId.get()));
49 }
50
51 NS_IMPL_ADDREF(nsMsgFilterList)
NS_IMPL_RELEASE(nsMsgFilterList)52 NS_IMPL_RELEASE(nsMsgFilterList)
53 NS_IMPL_QUERY_INTERFACE(nsMsgFilterList, nsIMsgFilterList)
54
55 NS_IMETHODIMP nsMsgFilterList::CreateFilter(const nsAString& name,
56 class nsIMsgFilter** aFilter) {
57 NS_ENSURE_ARG_POINTER(aFilter);
58
59 NS_ADDREF(*aFilter = new nsMsgFilter);
60
61 (*aFilter)->SetFilterName(name);
62 (*aFilter)->SetFilterList(this);
63
64 return NS_OK;
65 }
66
SetLoggingEnabled(bool enabled)67 NS_IMETHODIMP nsMsgFilterList::SetLoggingEnabled(bool enabled) {
68 if (!enabled) {
69 // Disabling logging has side effect of closing logfile (if open).
70 SetLogStream(nullptr);
71 }
72 m_loggingEnabled = enabled;
73 return NS_OK;
74 }
75
GetLoggingEnabled(bool * enabled)76 NS_IMETHODIMP nsMsgFilterList::GetLoggingEnabled(bool* enabled) {
77 *enabled = m_loggingEnabled;
78 return NS_OK;
79 }
80
GetListId(nsACString & aListId)81 NS_IMETHODIMP nsMsgFilterList::GetListId(nsACString& aListId) {
82 aListId.Assign(m_listId);
83 return NS_OK;
84 }
85
GetFolder(nsIMsgFolder ** aFolder)86 NS_IMETHODIMP nsMsgFilterList::GetFolder(nsIMsgFolder** aFolder) {
87 NS_ENSURE_ARG_POINTER(aFolder);
88
89 NS_IF_ADDREF(*aFolder = m_folder);
90 return NS_OK;
91 }
92
SetFolder(nsIMsgFolder * aFolder)93 NS_IMETHODIMP nsMsgFilterList::SetFolder(nsIMsgFolder* aFolder) {
94 m_folder = aFolder;
95 return NS_OK;
96 }
97
SaveToFile(nsIOutputStream * stream)98 NS_IMETHODIMP nsMsgFilterList::SaveToFile(nsIOutputStream* stream) {
99 if (!stream) return NS_ERROR_NULL_POINTER;
100 return SaveTextFilters(stream);
101 }
102
103 #define LOG_HEADER \
104 "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style " \
105 "type=\"text/css\">body{font-family:Consolas,\"Lucida " \
106 "Console\",Monaco,\"Courier " \
107 "New\",Courier,monospace;font-size:small}</style>\n</head>\n<body>\n"
108 #define LOG_HEADER_LEN (strlen(LOG_HEADER))
109
EnsureLogFile(nsIFile * file)110 nsresult nsMsgFilterList::EnsureLogFile(nsIFile* file) {
111 bool exists;
112 nsresult rv = file->Exists(&exists);
113 if (NS_SUCCEEDED(rv) && !exists) {
114 rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
115 NS_ENSURE_SUCCESS(rv, rv);
116 }
117
118 int64_t fileSize;
119 rv = file->GetFileSize(&fileSize);
120 NS_ENSURE_SUCCESS(rv, rv);
121
122 // write the header at the start
123 if (fileSize == 0) {
124 nsCOMPtr<nsIOutputStream> outputStream;
125 rv = MsgGetFileStream(file, getter_AddRefs(outputStream));
126 NS_ENSURE_SUCCESS(rv, rv);
127
128 uint32_t writeCount;
129 rv = outputStream->Write(LOG_HEADER, LOG_HEADER_LEN, &writeCount);
130 NS_ASSERTION(writeCount == LOG_HEADER_LEN,
131 "failed to write out log header");
132 NS_ENSURE_SUCCESS(rv, rv);
133 outputStream->Close();
134 }
135
136 return NS_OK;
137 }
138
ClearLog()139 NS_IMETHODIMP nsMsgFilterList::ClearLog() {
140 bool loggingEnabled = m_loggingEnabled;
141
142 // disable logging while clearing (and close logStream if open).
143 SetLoggingEnabled(false);
144
145 nsCOMPtr<nsIFile> file;
146 if (NS_SUCCEEDED(GetLogFile(getter_AddRefs(file)))) {
147 file->Remove(false);
148 // Recreate the file, with just the html header.
149 EnsureLogFile(file);
150 }
151
152 SetLoggingEnabled(loggingEnabled);
153 return NS_OK;
154 }
155
GetLogFile(nsIFile ** aFile)156 nsresult nsMsgFilterList::GetLogFile(nsIFile** aFile) {
157 NS_ENSURE_ARG_POINTER(aFile);
158
159 // XXX todo
160 // the path to the log file won't change
161 // should we cache it?
162 nsCOMPtr<nsIMsgIncomingServer> server;
163 nsresult rv = m_folder->GetServer(getter_AddRefs(server));
164 NS_ENSURE_SUCCESS(rv, rv);
165
166 nsCString type;
167 rv = server->GetType(type);
168 NS_ENSURE_SUCCESS(rv, rv);
169
170 bool isServer = false;
171 rv = m_folder->GetIsServer(&isServer);
172 NS_ENSURE_SUCCESS(rv, rv);
173
174 // for news folders (not servers), the filter file is
175 // mcom.test.dat
176 // where the summary file is
177 // mcom.test.msf
178 // since the log is an html file we make it
179 // mcom.test.htm
180 if (type.EqualsLiteral("nntp") && !isServer) {
181 nsCOMPtr<nsIFile> thisFolder;
182 rv = m_folder->GetFilePath(getter_AddRefs(thisFolder));
183 NS_ENSURE_SUCCESS(rv, rv);
184
185 nsCOMPtr<nsIFile> filterLogFile =
186 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
187 NS_ENSURE_SUCCESS(rv, rv);
188 rv = filterLogFile->InitWithFile(thisFolder);
189 NS_ENSURE_SUCCESS(rv, rv);
190
191 // NOTE:
192 // we don't we need to call NS_MsgHashIfNecessary()
193 // it's already been hashed, if necessary
194 nsAutoString filterLogName;
195 rv = filterLogFile->GetLeafName(filterLogName);
196 NS_ENSURE_SUCCESS(rv, rv);
197
198 filterLogName.AppendLiteral(u".htm");
199
200 rv = filterLogFile->SetLeafName(filterLogName);
201 NS_ENSURE_SUCCESS(rv, rv);
202
203 filterLogFile.forget(aFile);
204 } else {
205 rv = server->GetLocalPath(aFile);
206 NS_ENSURE_SUCCESS(rv, rv);
207
208 rv = (*aFile)->AppendNative("filterlog.html"_ns);
209 NS_ENSURE_SUCCESS(rv, rv);
210 }
211 return NS_OK;
212 }
213
214 NS_IMETHODIMP
GetLogURL(nsACString & aLogURL)215 nsMsgFilterList::GetLogURL(nsACString& aLogURL) {
216 nsCOMPtr<nsIFile> file;
217 nsresult rv = GetLogFile(getter_AddRefs(file));
218 NS_ENSURE_SUCCESS(rv, rv);
219
220 rv = NS_GetURLSpecFromFile(file, aLogURL);
221 NS_ENSURE_SUCCESS(rv, rv);
222
223 return !aLogURL.IsEmpty() ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
224 }
225
226 NS_IMETHODIMP
SetLogStream(nsIOutputStream * aLogStream)227 nsMsgFilterList::SetLogStream(nsIOutputStream* aLogStream) {
228 // if there is a log stream already, close it
229 if (m_logStream) {
230 m_logStream->Close(); // will flush
231 }
232
233 m_logStream = aLogStream;
234 return NS_OK;
235 }
236
237 NS_IMETHODIMP
GetLogStream(nsIOutputStream ** aLogStream)238 nsMsgFilterList::GetLogStream(nsIOutputStream** aLogStream) {
239 NS_ENSURE_ARG_POINTER(aLogStream);
240
241 if (!m_logStream && m_loggingEnabled) {
242 nsCOMPtr<nsIFile> logFile;
243 nsresult rv = GetLogFile(getter_AddRefs(logFile));
244 if (NS_SUCCEEDED(rv)) {
245 // Make sure it exists and has it's initial header.
246 rv = EnsureLogFile(logFile);
247 if (NS_SUCCEEDED(rv)) {
248 // append to the end of the log file
249 rv = MsgNewBufferedFileOutputStream(
250 getter_AddRefs(m_logStream), logFile,
251 PR_CREATE_FILE | PR_WRONLY | PR_APPEND, 0666);
252 }
253 }
254 if (NS_FAILED(rv)) {
255 m_logStream = nullptr;
256 }
257 }
258
259 // Always returns NS_OK. The stream can be null.
260 NS_IF_ADDREF(*aLogStream = m_logStream);
261 return NS_OK;
262 }
263
264 NS_IMETHODIMP
ApplyFiltersToHdr(nsMsgFilterTypeType filterType,nsIMsgDBHdr * msgHdr,nsIMsgFolder * folder,nsIMsgDatabase * db,const nsACString & headers,nsIMsgFilterHitNotify * listener,nsIMsgWindow * msgWindow)265 nsMsgFilterList::ApplyFiltersToHdr(nsMsgFilterTypeType filterType,
266 nsIMsgDBHdr* msgHdr, nsIMsgFolder* folder,
267 nsIMsgDatabase* db,
268 const nsACString& headers,
269 nsIMsgFilterHitNotify* listener,
270 nsIMsgWindow* msgWindow) {
271 MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
272 ("(Auto) nsMsgFilterList::ApplyFiltersToHdr"));
273 if (!msgHdr) {
274 // Sometimes we get here with no header, so let's not crash on that
275 // later on.
276 MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
277 ("(Auto) Called with NULL message header, nothing to do"));
278 return NS_ERROR_NULL_POINTER;
279 }
280
281 nsCOMPtr<nsIMsgFilter> filter;
282 uint32_t filterCount = 0;
283 nsresult rv = GetFilterCount(&filterCount);
284 NS_ENSURE_SUCCESS(rv, rv);
285
286 RefPtr<nsMsgSearchScopeTerm> scope =
287 new nsMsgSearchScopeTerm(nullptr, nsMsgSearchScope::offlineMail, folder);
288
289 nsString folderName;
290 folder->GetName(folderName);
291 nsMsgKey msgKey;
292 msgHdr->GetMessageKey(&msgKey);
293 nsCString typeName;
294 nsCOMPtr<nsIMsgFilterService> filterService =
295 do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
296 filterService->FilterTypeName(filterType, typeName);
297 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
298 ("(Auto) Filter run initiated, trigger=%s (%i)", typeName.get(),
299 filterType));
300 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
301 ("(Auto) Running %" PRIu32
302 " filters from %s on message with key %" PRIu32 " in folder '%s'",
303 filterCount, m_listId.get(), msgKeyToInt(msgKey),
304 NS_ConvertUTF16toUTF8(folderName).get()));
305
306 for (uint32_t filterIndex = 0; filterIndex < filterCount; filterIndex++) {
307 if (NS_SUCCEEDED(GetFilterAt(filterIndex, getter_AddRefs(filter)))) {
308 bool isEnabled;
309 nsMsgFilterTypeType curFilterType;
310
311 filter->GetEnabled(&isEnabled);
312 if (!isEnabled) {
313 // clang-format off
314 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
315 ("(Auto) Skipping disabled filter at index %" PRIu32,
316 filterIndex));
317 // clang-format on
318 continue;
319 }
320
321 nsString filterName;
322 filter->GetFilterName(filterName);
323 filter->GetFilterType(&curFilterType);
324 if (curFilterType & filterType) {
325 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
326 ("(Auto) Running filter %" PRIu32, filterIndex));
327 MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
328 ("(Auto) Filter name: %s",
329 NS_ConvertUTF16toUTF8(filterName).get()));
330
331 nsresult matchTermStatus = NS_OK;
332 bool result = false;
333
334 filter->SetScope(scope);
335 matchTermStatus =
336 filter->MatchHdr(msgHdr, folder, db, headers, &result);
337 filter->SetScope(nullptr);
338 if (NS_SUCCEEDED(matchTermStatus) && result && listener) {
339 nsCString msgId;
340 msgHdr->GetMessageId(getter_Copies(msgId));
341 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
342 ("(Auto) Filter matched message with key %" PRIu32,
343 msgKeyToInt(msgKey)));
344 MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
345 ("(Auto) Matched message ID: %s", msgId.get()));
346
347 bool applyMore = true;
348 rv = listener->ApplyFilterHit(filter, msgWindow, &applyMore);
349 if (NS_FAILED(rv)) {
350 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
351 ("(Auto) Applying filter actions failed"));
352 LogFilterMessage(u"Applying filter actions failed"_ns, filter);
353 } else {
354 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
355 ("(Auto) Applying filter actions succeeded"));
356 }
357 if (NS_FAILED(rv) || !applyMore) {
358 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
359 ("(Auto) Stopping further filter execution"
360 " on this message"));
361 break;
362 }
363 } else {
364 if (NS_FAILED(matchTermStatus)) {
365 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
366 ("(Auto) Filter evaluation failed"));
367 LogFilterMessage(u"Filter evaluation failed"_ns, filter);
368 }
369 if (!result)
370 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
371 ("(Auto) Filter didn't match"));
372 }
373 } else {
374 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
375 ("(Auto) Skipping filter of non-matching type"
376 " at index %" PRIu32,
377 filterIndex));
378 }
379 }
380 }
381 if (NS_FAILED(rv)) {
382 // clang-format off
383 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
384 ("(Auto) Filter run failed (%" PRIx32 ")", static_cast<uint32_t>(rv)));
385 // clang-format on
386 LogFilterMessage(u"Filter run failed"_ns, nullptr);
387 }
388 return rv;
389 }
390
391 NS_IMETHODIMP
SetDefaultFile(nsIFile * aFile)392 nsMsgFilterList::SetDefaultFile(nsIFile* aFile) {
393 m_defaultFile = aFile;
394 return NS_OK;
395 }
396
397 NS_IMETHODIMP
GetDefaultFile(nsIFile ** aResult)398 nsMsgFilterList::GetDefaultFile(nsIFile** aResult) {
399 NS_ENSURE_ARG_POINTER(aResult);
400
401 NS_IF_ADDREF(*aResult = m_defaultFile);
402 return NS_OK;
403 }
404
405 NS_IMETHODIMP
SaveToDefaultFile()406 nsMsgFilterList::SaveToDefaultFile() {
407 nsresult rv;
408 nsCOMPtr<nsIMsgFilterService> filterService =
409 do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
410 NS_ENSURE_SUCCESS(rv, rv);
411
412 return filterService->SaveFilterList(this, m_defaultFile);
413 }
414
415 typedef struct {
416 nsMsgFilterFileAttribValue attrib;
417 const char* attribName;
418 } FilterFileAttribEntry;
419
420 static FilterFileAttribEntry FilterFileAttribTable[] = {
421 {nsIMsgFilterList::attribNone, ""},
422 {nsIMsgFilterList::attribVersion, "version"},
423 {nsIMsgFilterList::attribLogging, "logging"},
424 {nsIMsgFilterList::attribName, "name"},
425 {nsIMsgFilterList::attribEnabled, "enabled"},
426 {nsIMsgFilterList::attribDescription, "description"},
427 {nsIMsgFilterList::attribType, "type"},
428 {nsIMsgFilterList::attribScriptFile, "scriptName"},
429 {nsIMsgFilterList::attribAction, "action"},
430 {nsIMsgFilterList::attribActionValue, "actionValue"},
431 {nsIMsgFilterList::attribCondition, "condition"},
432 {nsIMsgFilterList::attribCustomId, "customId"},
433 };
434
435 static const unsigned int sNumFilterFileAttribTable =
436 MOZ_ARRAY_LENGTH(FilterFileAttribTable);
437
438 // If we want to buffer file IO, wrap it in here.
ReadChar(nsIInputStream * aStream)439 int nsMsgFilterList::ReadChar(nsIInputStream* aStream) {
440 char newChar;
441 uint32_t bytesRead;
442 uint64_t bytesAvailable;
443 nsresult rv = aStream->Available(&bytesAvailable);
444 if (NS_FAILED(rv) || bytesAvailable == 0) return EOF_CHAR;
445
446 rv = aStream->Read(&newChar, 1, &bytesRead);
447 if (NS_FAILED(rv) || !bytesRead) return EOF_CHAR;
448
449 if (m_startWritingToBuffer) m_unparsedFilterBuffer.Append(newChar);
450 return (unsigned char)newChar; // Make sure the char is unsigned.
451 }
452
SkipWhitespace(nsIInputStream * aStream)453 int nsMsgFilterList::SkipWhitespace(nsIInputStream* aStream) {
454 int ch;
455 do {
456 ch = ReadChar(aStream);
457 } while (!(ch & 0x80) &&
458 isspace(ch)); // isspace can crash with non-ascii input
459
460 return ch;
461 }
462
StrToBool(nsCString & str)463 bool nsMsgFilterList::StrToBool(nsCString& str) {
464 return str.EqualsLiteral("yes");
465 }
466
LoadAttrib(nsMsgFilterFileAttribValue & attrib,nsIInputStream * aStream)467 int nsMsgFilterList::LoadAttrib(nsMsgFilterFileAttribValue& attrib,
468 nsIInputStream* aStream) {
469 char attribStr[100];
470 int curChar;
471 attrib = nsIMsgFilterList::attribNone;
472
473 curChar = SkipWhitespace(aStream);
474 int i;
475 for (i = 0; i + 1 < (int)(sizeof(attribStr));) {
476 if (curChar == EOF_CHAR || (!(curChar & 0x80) && isspace(curChar)) ||
477 curChar == '=')
478 break;
479 attribStr[i++] = curChar;
480 curChar = ReadChar(aStream);
481 }
482 attribStr[i] = '\0';
483 for (unsigned int tableIndex = 0; tableIndex < sNumFilterFileAttribTable;
484 tableIndex++) {
485 if (!PL_strcasecmp(attribStr,
486 FilterFileAttribTable[tableIndex].attribName)) {
487 attrib = FilterFileAttribTable[tableIndex].attrib;
488 break;
489 }
490 }
491 return curChar;
492 }
493
GetStringForAttrib(nsMsgFilterFileAttribValue attrib)494 const char* nsMsgFilterList::GetStringForAttrib(
495 nsMsgFilterFileAttribValue attrib) {
496 for (unsigned int tableIndex = 0; tableIndex < sNumFilterFileAttribTable;
497 tableIndex++) {
498 if (attrib == FilterFileAttribTable[tableIndex].attrib)
499 return FilterFileAttribTable[tableIndex].attribName;
500 }
501 return nullptr;
502 }
503
LoadValue(nsCString & value,nsIInputStream * aStream)504 nsresult nsMsgFilterList::LoadValue(nsCString& value, nsIInputStream* aStream) {
505 nsAutoCString valueStr;
506 int curChar;
507 value = "";
508 curChar = SkipWhitespace(aStream);
509 if (curChar != '"') {
510 NS_ASSERTION(false, "expecting quote as start of value");
511 return NS_MSG_FILTER_PARSE_ERROR;
512 }
513 curChar = ReadChar(aStream);
514 do {
515 if (curChar == '\\') {
516 int nextChar = ReadChar(aStream);
517 if (nextChar == '"')
518 curChar = '"';
519 else if (nextChar == '\\') // replace "\\" with "\"
520 {
521 valueStr += curChar;
522 curChar = ReadChar(aStream);
523 } else {
524 valueStr += curChar;
525 curChar = nextChar;
526 }
527 } else {
528 if (curChar == EOF_CHAR || curChar == '"' || curChar == '\n' ||
529 curChar == '\r') {
530 value += valueStr;
531 break;
532 }
533 }
534 valueStr += curChar;
535 curChar = ReadChar(aStream);
536 } while (curChar != EOF_CHAR);
537 return NS_OK;
538 }
539
LoadTextFilters(already_AddRefed<nsIInputStream> aStream)540 nsresult nsMsgFilterList::LoadTextFilters(
541 already_AddRefed<nsIInputStream> aStream) {
542 nsresult err = NS_OK;
543 uint64_t bytesAvailable;
544
545 nsCOMPtr<nsIInputStream> bufStream;
546 nsCOMPtr<nsIInputStream> stream = std::move(aStream);
547 err = NS_NewBufferedInputStream(getter_AddRefs(bufStream), stream.forget(),
548 FILE_IO_BUFFER_SIZE);
549 NS_ENSURE_SUCCESS(err, err);
550
551 nsMsgFilterFileAttribValue attrib;
552 nsCOMPtr<nsIMsgRuleAction> currentFilterAction;
553 // We'd really like to move lot's of these into the objects that they refer
554 // to.
555 do {
556 nsAutoCString value;
557 nsresult intToStringResult;
558
559 int curChar;
560 curChar = LoadAttrib(attrib, bufStream);
561 if (curChar == EOF_CHAR) // reached eof
562 break;
563 err = LoadValue(value, bufStream);
564 if (NS_FAILED(err)) break;
565
566 switch (attrib) {
567 case nsIMsgFilterList::attribNone:
568 if (m_curFilter) m_curFilter->SetUnparseable(true);
569 break;
570 case nsIMsgFilterList::attribVersion:
571 m_fileVersion = value.ToInteger(&intToStringResult);
572 if (NS_FAILED(intToStringResult)) {
573 attrib = nsIMsgFilterList::attribNone;
574 NS_ASSERTION(false, "error parsing filter file version");
575 }
576 break;
577 case nsIMsgFilterList::attribLogging:
578 m_loggingEnabled = StrToBool(value);
579 // We are going to buffer each filter as we read them.
580 // Make sure no garbage is there
581 m_unparsedFilterBuffer.Truncate();
582 m_startWritingToBuffer = true; // filters begin now
583 break;
584 case nsIMsgFilterList::attribName: // every filter starts w/ a name
585 {
586 if (m_curFilter) {
587 int32_t nextFilterStartPos = m_unparsedFilterBuffer.RFind("name");
588
589 nsAutoCString nextFilterPart;
590 nextFilterPart = Substring(m_unparsedFilterBuffer, nextFilterStartPos,
591 m_unparsedFilterBuffer.Length());
592 m_unparsedFilterBuffer.SetLength(nextFilterStartPos);
593
594 bool unparseableFilter;
595 m_curFilter->GetUnparseable(&unparseableFilter);
596 if (unparseableFilter) {
597 m_curFilter->SetUnparsedBuffer(m_unparsedFilterBuffer);
598 m_curFilter->SetEnabled(false); // disable the filter because we
599 // don't know how to apply it
600 }
601 m_unparsedFilterBuffer = nextFilterPart;
602 }
603 nsMsgFilter* filter = new nsMsgFilter;
604 if (filter == nullptr) {
605 err = NS_ERROR_OUT_OF_MEMORY;
606 break;
607 }
608 filter->SetFilterList(static_cast<nsIMsgFilterList*>(this));
609 nsAutoString unicodeStr;
610 if (m_fileVersion == k45Version) {
611 NS_CopyNativeToUnicode(value, unicodeStr);
612 filter->SetFilterName(unicodeStr);
613 } else {
614 CopyUTF8toUTF16(value, unicodeStr);
615 filter->SetFilterName(unicodeStr);
616 }
617 m_curFilter = filter;
618 m_filters.AppendElement(filter);
619 } break;
620 case nsIMsgFilterList::attribEnabled:
621 if (m_curFilter) m_curFilter->SetEnabled(StrToBool(value));
622 break;
623 case nsIMsgFilterList::attribDescription:
624 if (m_curFilter) m_curFilter->SetFilterDesc(value);
625 break;
626 case nsIMsgFilterList::attribType:
627 if (m_curFilter) {
628 // Older versions of filters didn't have the ability to turn on/off
629 // the manual filter context, so default manual to be on in that case
630 int32_t filterType = value.ToInteger(&intToStringResult);
631 if (m_fileVersion < kManualContextVersion)
632 filterType |= nsMsgFilterType::Manual;
633 m_curFilter->SetType((nsMsgFilterTypeType)filterType);
634 }
635 break;
636 case nsIMsgFilterList::attribScriptFile:
637 if (m_curFilter) m_curFilter->SetFilterScript(&value);
638 break;
639 case nsIMsgFilterList::attribAction:
640 if (m_curFilter) {
641 nsMsgRuleActionType actionType =
642 nsMsgFilter::GetActionForFilingStr(value);
643 if (actionType == nsMsgFilterAction::None)
644 m_curFilter->SetUnparseable(true);
645 else {
646 err =
647 m_curFilter->CreateAction(getter_AddRefs(currentFilterAction));
648 NS_ENSURE_SUCCESS(err, err);
649 currentFilterAction->SetType(actionType);
650 m_curFilter->AppendAction(currentFilterAction);
651 }
652 }
653 break;
654 case nsIMsgFilterList::attribActionValue:
655 if (m_curFilter && currentFilterAction) {
656 nsMsgRuleActionType type;
657 currentFilterAction->GetType(&type);
658 if (type == nsMsgFilterAction::MoveToFolder ||
659 type == nsMsgFilterAction::CopyToFolder)
660 err = m_curFilter->ConvertMoveOrCopyToFolderValue(
661 currentFilterAction, value);
662 else if (type == nsMsgFilterAction::ChangePriority) {
663 nsMsgPriorityValue outPriority;
664 nsresult res =
665 NS_MsgGetPriorityFromString(value.get(), outPriority);
666 if (NS_SUCCEEDED(res))
667 currentFilterAction->SetPriority(outPriority);
668 else
669 NS_ASSERTION(false, "invalid priority in filter file");
670 } else if (type == nsMsgFilterAction::Label) {
671 // upgrade label to corresponding tag/keyword
672 nsresult res;
673 int32_t labelInt = value.ToInteger(&res);
674 if (NS_SUCCEEDED(res)) {
675 nsAutoCString keyword("$label");
676 keyword.Append('0' + labelInt);
677 currentFilterAction->SetType(nsMsgFilterAction::AddTag);
678 currentFilterAction->SetStrValue(keyword);
679 }
680 } else if (type == nsMsgFilterAction::JunkScore) {
681 nsresult res;
682 int32_t junkScore = value.ToInteger(&res);
683 if (NS_SUCCEEDED(res)) currentFilterAction->SetJunkScore(junkScore);
684 } else if (type == nsMsgFilterAction::Forward ||
685 type == nsMsgFilterAction::Reply ||
686 type == nsMsgFilterAction::AddTag ||
687 type == nsMsgFilterAction::Custom) {
688 currentFilterAction->SetStrValue(value);
689 }
690 }
691 break;
692 case nsIMsgFilterList::attribCondition:
693 if (m_curFilter) {
694 if (m_fileVersion == k45Version) {
695 nsAutoString unicodeStr;
696 NS_CopyNativeToUnicode(value, unicodeStr);
697 CopyUTF16toUTF8(unicodeStr, value);
698 }
699 err = ParseCondition(m_curFilter, value.get());
700 if (err == NS_ERROR_INVALID_ARG)
701 err = m_curFilter->SetUnparseable(true);
702 NS_ENSURE_SUCCESS(err, err);
703 }
704 break;
705 case nsIMsgFilterList::attribCustomId:
706 if (m_curFilter && currentFilterAction) {
707 err = currentFilterAction->SetCustomId(value);
708 NS_ENSURE_SUCCESS(err, err);
709 }
710 break;
711 }
712 } while (NS_SUCCEEDED(bufStream->Available(&bytesAvailable)));
713
714 if (m_curFilter) {
715 bool unparseableFilter;
716 m_curFilter->GetUnparseable(&unparseableFilter);
717 if (unparseableFilter) {
718 m_curFilter->SetUnparsedBuffer(m_unparsedFilterBuffer);
719 m_curFilter->SetEnabled(
720 false); // disable the filter because we don't know how to apply it
721 }
722 }
723
724 return err;
725 }
726
727 // parse condition like "(subject, contains, fred) AND (body, isn't, "foo)")"
728 // values with close parens will be quoted.
729 // what about values with close parens and quotes? e.g., (body, isn't, "foo")")
730 // I guess interior quotes will need to be escaped - ("foo\")")
731 // which will get written out as (\"foo\\")\") and read in as ("foo\")"
732 // ALL means match all messages.
ParseCondition(nsIMsgFilter * aFilter,const char * aCondition)733 NS_IMETHODIMP nsMsgFilterList::ParseCondition(nsIMsgFilter* aFilter,
734 const char* aCondition) {
735 NS_ENSURE_ARG_POINTER(aFilter);
736
737 bool done = false;
738 nsresult err = NS_OK;
739 const char* curPtr = aCondition;
740 if (!strcmp(aCondition, "ALL")) {
741 RefPtr<nsMsgSearchTerm> newTerm = new nsMsgSearchTerm;
742 newTerm->m_matchAll = true;
743 aFilter->AppendTerm(newTerm);
744 return NS_OK;
745 }
746
747 while (!done) {
748 // insert code to save the boolean operator if there is one for this search
749 // term....
750 const char* openParen = PL_strchr(curPtr, '(');
751 const char* orTermPos = PL_strchr(
752 curPtr, 'O'); // determine if an "OR" appears b4 the openParen...
753 bool ANDTerm = true;
754 if (orTermPos &&
755 orTermPos < openParen) // make sure OR term falls before the '('
756 ANDTerm = false;
757
758 char* termDup = nullptr;
759 if (openParen) {
760 bool foundEndTerm = false;
761 bool inQuote = false;
762 for (curPtr = openParen + 1; *curPtr; curPtr++) {
763 if (*curPtr == '\\' && *(curPtr + 1) == '"')
764 curPtr++;
765 else if (*curPtr == ')' && !inQuote) {
766 foundEndTerm = true;
767 break;
768 } else if (*curPtr == '"')
769 inQuote = !inQuote;
770 }
771 if (foundEndTerm) {
772 int termLen = curPtr - openParen - 1;
773 termDup = (char*)PR_Malloc(termLen + 1);
774 if (termDup) {
775 PL_strncpy(termDup, openParen + 1, termLen + 1);
776 termDup[termLen] = '\0';
777 } else {
778 err = NS_ERROR_OUT_OF_MEMORY;
779 break;
780 }
781 }
782 } else
783 break;
784 if (termDup) {
785 RefPtr<nsMsgSearchTerm> newTerm = new nsMsgSearchTerm;
786 // Invert nsMsgSearchTerm::EscapeQuotesInStr()
787 for (char *to = termDup, *from = termDup;;) {
788 if (*from == '\\' && from[1] == '"') from++;
789 if (!(*to++ = *from++)) break;
790 }
791 newTerm->m_booleanOp = (ANDTerm) ? nsMsgSearchBooleanOp::BooleanAND
792 : nsMsgSearchBooleanOp::BooleanOR;
793
794 err = newTerm->DeStreamNew(termDup, PL_strlen(termDup));
795 NS_ENSURE_SUCCESS(err, err);
796 aFilter->AppendTerm(newTerm);
797 PR_FREEIF(termDup);
798 } else
799 break;
800 }
801 return err;
802 }
803
WriteIntAttr(nsMsgFilterFileAttribValue attrib,int value,nsIOutputStream * aStream)804 nsresult nsMsgFilterList::WriteIntAttr(nsMsgFilterFileAttribValue attrib,
805 int value, nsIOutputStream* aStream) {
806 nsresult rv = NS_OK;
807 const char* attribStr = GetStringForAttrib(attrib);
808 if (attribStr) {
809 uint32_t bytesWritten;
810 nsAutoCString writeStr(attribStr);
811 writeStr.AppendLiteral("=\"");
812 writeStr.AppendInt(value);
813 writeStr.AppendLiteral("\"" MSG_LINEBREAK);
814 rv = aStream->Write(writeStr.get(), writeStr.Length(), &bytesWritten);
815 }
816 return rv;
817 }
818
819 NS_IMETHODIMP
WriteStrAttr(nsMsgFilterFileAttribValue attrib,const char * aStr,nsIOutputStream * aStream)820 nsMsgFilterList::WriteStrAttr(nsMsgFilterFileAttribValue attrib,
821 const char* aStr, nsIOutputStream* aStream) {
822 nsresult rv = NS_OK;
823 if (aStr && *aStr &&
824 aStream) // only proceed if we actually have a string to write out.
825 {
826 char* escapedStr = nullptr;
827 if (PL_strchr(aStr, '"'))
828 escapedStr = nsMsgSearchTerm::EscapeQuotesInStr(aStr);
829
830 const char* attribStr = GetStringForAttrib(attrib);
831 if (attribStr) {
832 uint32_t bytesWritten;
833 nsAutoCString writeStr(attribStr);
834 writeStr.AppendLiteral("=\"");
835 writeStr.Append((escapedStr) ? escapedStr : aStr);
836 writeStr.AppendLiteral("\"" MSG_LINEBREAK);
837 rv = aStream->Write(writeStr.get(), writeStr.Length(), &bytesWritten);
838 }
839 PR_Free(escapedStr);
840 }
841 return rv;
842 }
843
WriteBoolAttr(nsMsgFilterFileAttribValue attrib,bool boolVal,nsIOutputStream * aStream)844 nsresult nsMsgFilterList::WriteBoolAttr(nsMsgFilterFileAttribValue attrib,
845 bool boolVal,
846 nsIOutputStream* aStream) {
847 return WriteStrAttr(attrib, (boolVal) ? "yes" : "no", aStream);
848 }
849
WriteWstrAttr(nsMsgFilterFileAttribValue attrib,const char16_t * aFilterName,nsIOutputStream * aStream)850 nsresult nsMsgFilterList::WriteWstrAttr(nsMsgFilterFileAttribValue attrib,
851 const char16_t* aFilterName,
852 nsIOutputStream* aStream) {
853 WriteStrAttr(attrib, NS_ConvertUTF16toUTF8(aFilterName).get(), aStream);
854 return NS_OK;
855 }
856
SaveTextFilters(nsIOutputStream * aStream)857 nsresult nsMsgFilterList::SaveTextFilters(nsIOutputStream* aStream) {
858 uint32_t filterCount = 0;
859 nsresult err = GetFilterCount(&filterCount);
860 NS_ENSURE_SUCCESS(err, err);
861
862 err = WriteIntAttr(nsIMsgFilterList::attribVersion, kFileVersion, aStream);
863 NS_ENSURE_SUCCESS(err, err);
864 err =
865 WriteBoolAttr(nsIMsgFilterList::attribLogging, m_loggingEnabled, aStream);
866 NS_ENSURE_SUCCESS(err, err);
867 for (uint32_t i = 0; i < filterCount; i++) {
868 nsCOMPtr<nsIMsgFilter> filter;
869 if (NS_SUCCEEDED(GetFilterAt(i, getter_AddRefs(filter))) && filter) {
870 filter->SetFilterList(this);
871
872 // if the filter is temporary, don't write it to disk
873 bool isTemporary;
874 err = filter->GetTemporary(&isTemporary);
875 if (NS_SUCCEEDED(err) && !isTemporary) {
876 err = filter->SaveToTextFile(aStream);
877 if (NS_FAILED(err)) break;
878 }
879 } else
880 break;
881 }
882 if (NS_SUCCEEDED(err)) m_arbitraryHeaders.Truncate();
883 return err;
884 }
885
~nsMsgFilterList()886 nsMsgFilterList::~nsMsgFilterList() {
887 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
888 ("Closing filter list %s", m_listId.get()));
889 }
890
Close()891 nsresult nsMsgFilterList::Close() { return NS_ERROR_NOT_IMPLEMENTED; }
892
GetFilterCount(uint32_t * pCount)893 nsresult nsMsgFilterList::GetFilterCount(uint32_t* pCount) {
894 NS_ENSURE_ARG_POINTER(pCount);
895
896 *pCount = m_filters.Length();
897 return NS_OK;
898 }
899
GetFilterAt(uint32_t filterIndex,nsIMsgFilter ** filter)900 nsresult nsMsgFilterList::GetFilterAt(uint32_t filterIndex,
901 nsIMsgFilter** filter) {
902 NS_ENSURE_ARG_POINTER(filter);
903
904 uint32_t filterCount = 0;
905 GetFilterCount(&filterCount);
906 NS_ENSURE_ARG(filterIndex < filterCount);
907
908 NS_IF_ADDREF(*filter = m_filters[filterIndex]);
909 return NS_OK;
910 }
911
GetFilterNamed(const nsAString & aName,nsIMsgFilter ** aResult)912 nsresult nsMsgFilterList::GetFilterNamed(const nsAString& aName,
913 nsIMsgFilter** aResult) {
914 NS_ENSURE_ARG_POINTER(aResult);
915
916 uint32_t count = 0;
917 nsresult rv = GetFilterCount(&count);
918 NS_ENSURE_SUCCESS(rv, rv);
919
920 *aResult = nullptr;
921 for (uint32_t i = 0; i < count; i++) {
922 nsCOMPtr<nsIMsgFilter> filter;
923 rv = GetFilterAt(i, getter_AddRefs(filter));
924 if (NS_FAILED(rv)) continue;
925
926 nsString filterName;
927 filter->GetFilterName(filterName);
928 if (filterName.Equals(aName)) {
929 filter.forget(aResult);
930 break;
931 }
932 }
933
934 return NS_OK;
935 }
936
SetFilterAt(uint32_t filterIndex,nsIMsgFilter * filter)937 nsresult nsMsgFilterList::SetFilterAt(uint32_t filterIndex,
938 nsIMsgFilter* filter) {
939 m_filters[filterIndex] = filter;
940 return NS_OK;
941 }
942
RemoveFilterAt(uint32_t filterIndex)943 nsresult nsMsgFilterList::RemoveFilterAt(uint32_t filterIndex) {
944 m_filters.RemoveElementAt(filterIndex);
945 return NS_OK;
946 }
947
RemoveFilter(nsIMsgFilter * aFilter)948 nsresult nsMsgFilterList::RemoveFilter(nsIMsgFilter* aFilter) {
949 m_filters.RemoveElement(aFilter);
950 return NS_OK;
951 }
952
InsertFilterAt(uint32_t filterIndex,nsIMsgFilter * aFilter)953 nsresult nsMsgFilterList::InsertFilterAt(uint32_t filterIndex,
954 nsIMsgFilter* aFilter) {
955 if (!m_temporaryList) aFilter->SetFilterList(this);
956 m_filters.InsertElementAt(filterIndex, aFilter);
957
958 return NS_OK;
959 }
960
961 // Attempt to move the filter at index filterIndex in the specified direction.
962 // If motion not possible in that direction, we still return success.
963 // We could return an error if the FE's want to beep or something.
MoveFilterAt(uint32_t filterIndex,nsMsgFilterMotionValue motion)964 nsresult nsMsgFilterList::MoveFilterAt(uint32_t filterIndex,
965 nsMsgFilterMotionValue motion) {
966 NS_ENSURE_ARG((motion == nsMsgFilterMotion::up) ||
967 (motion == nsMsgFilterMotion::down));
968
969 uint32_t filterCount = 0;
970 nsresult rv = GetFilterCount(&filterCount);
971 NS_ENSURE_SUCCESS(rv, rv);
972
973 NS_ENSURE_ARG(filterIndex < filterCount);
974
975 uint32_t newIndex = filterIndex;
976
977 if (motion == nsMsgFilterMotion::up) {
978 // are we already at the top?
979 if (filterIndex == 0) return NS_OK;
980
981 newIndex = filterIndex - 1;
982 } else if (motion == nsMsgFilterMotion::down) {
983 // are we already at the bottom?
984 if (filterIndex == filterCount - 1) return NS_OK;
985
986 newIndex = filterIndex + 1;
987 }
988
989 nsCOMPtr<nsIMsgFilter> tempFilter1;
990 rv = GetFilterAt(newIndex, getter_AddRefs(tempFilter1));
991 NS_ENSURE_SUCCESS(rv, rv);
992
993 nsCOMPtr<nsIMsgFilter> tempFilter2;
994 rv = GetFilterAt(filterIndex, getter_AddRefs(tempFilter2));
995 NS_ENSURE_SUCCESS(rv, rv);
996
997 SetFilterAt(newIndex, tempFilter2);
998 SetFilterAt(filterIndex, tempFilter1);
999
1000 return NS_OK;
1001 }
1002
MoveFilter(nsIMsgFilter * aFilter,nsMsgFilterMotionValue motion)1003 nsresult nsMsgFilterList::MoveFilter(nsIMsgFilter* aFilter,
1004 nsMsgFilterMotionValue motion) {
1005 size_t filterIndex = m_filters.IndexOf(aFilter, 0);
1006 NS_ENSURE_ARG(filterIndex != m_filters.NoIndex);
1007
1008 return MoveFilterAt(filterIndex, motion);
1009 }
1010
GetVersion(int16_t * aResult)1011 nsresult nsMsgFilterList::GetVersion(int16_t* aResult) {
1012 NS_ENSURE_ARG_POINTER(aResult);
1013 *aResult = m_fileVersion;
1014 return NS_OK;
1015 }
1016
MatchOrChangeFilterTarget(const nsACString & oldFolderUri,const nsACString & newFolderUri,bool caseInsensitive,bool * found)1017 NS_IMETHODIMP nsMsgFilterList::MatchOrChangeFilterTarget(
1018 const nsACString& oldFolderUri, const nsACString& newFolderUri,
1019 bool caseInsensitive, bool* found) {
1020 NS_ENSURE_ARG_POINTER(found);
1021
1022 uint32_t numFilters = 0;
1023 nsresult rv = GetFilterCount(&numFilters);
1024 NS_ENSURE_SUCCESS(rv, rv);
1025
1026 nsCOMPtr<nsIMsgFilter> filter;
1027 nsCString folderUri;
1028 *found = false;
1029 for (uint32_t index = 0; index < numFilters; index++) {
1030 rv = GetFilterAt(index, getter_AddRefs(filter));
1031 NS_ENSURE_SUCCESS(rv, rv);
1032
1033 uint32_t numActions;
1034 rv = filter->GetActionCount(&numActions);
1035 NS_ENSURE_SUCCESS(rv, rv);
1036
1037 for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++) {
1038 nsCOMPtr<nsIMsgRuleAction> filterAction;
1039 rv = filter->GetActionAt(actionIndex, getter_AddRefs(filterAction));
1040 if (NS_FAILED(rv) || !filterAction) continue;
1041
1042 nsMsgRuleActionType actionType;
1043 if (NS_FAILED(filterAction->GetType(&actionType))) continue;
1044
1045 if (actionType == nsMsgFilterAction::MoveToFolder ||
1046 actionType == nsMsgFilterAction::CopyToFolder) {
1047 rv = filterAction->GetTargetFolderUri(folderUri);
1048 if (NS_SUCCEEDED(rv) && !folderUri.IsEmpty()) {
1049 bool matchFound = false;
1050 if (caseInsensitive) {
1051 if (folderUri.Equals(oldFolderUri,
1052 nsCaseInsensitiveCStringComparator)) // local
1053 matchFound = true;
1054 } else {
1055 if (folderUri.Equals(oldFolderUri)) // imap
1056 matchFound = true;
1057 }
1058 if (matchFound) {
1059 *found = true;
1060 // if we just want to match the uri's, newFolderUri will be null
1061 if (!newFolderUri.IsEmpty()) {
1062 rv = filterAction->SetTargetFolderUri(newFolderUri);
1063 NS_ENSURE_SUCCESS(rv, rv);
1064 }
1065 }
1066 }
1067 }
1068 }
1069 }
1070 return rv;
1071 }
1072
1073 // this would only return true if any filter was on "any header", which we
1074 // don't support in 6.x
GetShouldDownloadAllHeaders(bool * aResult)1075 NS_IMETHODIMP nsMsgFilterList::GetShouldDownloadAllHeaders(bool* aResult) {
1076 NS_ENSURE_ARG_POINTER(aResult);
1077
1078 *aResult = false;
1079 return NS_OK;
1080 }
1081
1082 // leaves m_arbitraryHeaders filed in with the arbitrary headers.
ComputeArbitraryHeaders()1083 nsresult nsMsgFilterList::ComputeArbitraryHeaders() {
1084 NS_ENSURE_TRUE(m_arbitraryHeaders.IsEmpty(), NS_OK);
1085
1086 uint32_t numFilters = 0;
1087 nsresult rv = GetFilterCount(&numFilters);
1088 NS_ENSURE_SUCCESS(rv, rv);
1089
1090 nsCOMPtr<nsIMsgFilter> filter;
1091 nsMsgSearchAttribValue attrib;
1092 nsCString arbitraryHeader;
1093 for (uint32_t index = 0; index < numFilters; index++) {
1094 rv = GetFilterAt(index, getter_AddRefs(filter));
1095 if (!(NS_SUCCEEDED(rv) && filter)) continue;
1096
1097 nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms;
1098 filter->GetSearchTerms(searchTerms);
1099 for (uint32_t i = 0; i < searchTerms.Length(); i++) {
1100 filter->GetTerm(i, &attrib, nullptr, nullptr, nullptr, arbitraryHeader);
1101 if (!arbitraryHeader.IsEmpty()) {
1102 if (m_arbitraryHeaders.IsEmpty())
1103 m_arbitraryHeaders.Assign(arbitraryHeader);
1104 else if (m_arbitraryHeaders.Find(arbitraryHeader,
1105 /* ignoreCase = */ true) == -1) {
1106 m_arbitraryHeaders.Append(' ');
1107 m_arbitraryHeaders.Append(arbitraryHeader);
1108 }
1109 }
1110 }
1111 }
1112
1113 return NS_OK;
1114 }
1115
GetArbitraryHeaders(nsACString & aResult)1116 NS_IMETHODIMP nsMsgFilterList::GetArbitraryHeaders(nsACString& aResult) {
1117 ComputeArbitraryHeaders();
1118 aResult = m_arbitraryHeaders;
1119 return NS_OK;
1120 }
1121
FlushLogIfNecessary()1122 NS_IMETHODIMP nsMsgFilterList::FlushLogIfNecessary() {
1123 // only flush the log if we are logging
1124 if (m_loggingEnabled && m_logStream) {
1125 nsresult rv = m_logStream->Flush();
1126 NS_ENSURE_SUCCESS(rv, rv);
1127 }
1128 return NS_OK;
1129 }
1130
1131 #define LOG_ENTRY_START_TAG "<p>\n"
1132 #define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG))
1133 #define LOG_ENTRY_END_TAG "</p>\n"
1134 #define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG))
1135
LogFilterMessage(const nsAString & message,nsIMsgFilter * filter)1136 NS_IMETHODIMP nsMsgFilterList::LogFilterMessage(const nsAString& message,
1137 nsIMsgFilter* filter) {
1138 if (!m_loggingEnabled) {
1139 return NS_OK;
1140 }
1141 nsCOMPtr<nsIOutputStream> logStream;
1142 GetLogStream(getter_AddRefs(logStream));
1143 if (!logStream) {
1144 // Logging is on, but we failed to access the filter logfile.
1145 // For completeness, we'll return an error, but we don't expect anyone
1146 // to ever check it - logging failures shouldn't stop anything else.
1147 return NS_ERROR_FAILURE;
1148 }
1149
1150 nsCOMPtr<nsIStringBundleService> bundleService =
1151 mozilla::services::GetStringBundleService();
1152 NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
1153
1154 nsCOMPtr<nsIStringBundle> bundle;
1155 nsresult rv = bundleService->CreateBundle(
1156 "chrome://messenger/locale/filter.properties", getter_AddRefs(bundle));
1157 NS_ENSURE_SUCCESS(rv, rv);
1158
1159 nsString tempMessage(message);
1160
1161 if (filter) {
1162 // If a filter was passed, prepend its name in the log message.
1163 nsString filterName;
1164 filter->GetFilterName(filterName);
1165
1166 AutoTArray<nsString, 2> logFormatStrings = {filterName, tempMessage};
1167 nsString statusLogMessage;
1168 rv = bundle->FormatStringFromName("filterMessage", logFormatStrings,
1169 statusLogMessage);
1170 if (NS_SUCCEEDED(rv)) tempMessage.Assign(statusLogMessage);
1171 }
1172
1173 // Prepare timestamp
1174 PRExplodedTime exploded;
1175 nsString dateValue;
1176 PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
1177 mozilla::DateTimeFormat::FormatPRExplodedTime(mozilla::kDateFormatShort,
1178 mozilla::kTimeFormatLong,
1179 &exploded, dateValue);
1180
1181 // HTML-escape the log for security reasons.
1182 // We don't want someone to send us a message with a subject with
1183 // HTML tags, especially <script>.
1184 nsCString escapedBuffer;
1185 nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(tempMessage), escapedBuffer);
1186
1187 // Print timestamp and the message.
1188 AutoTArray<nsString, 2> logFormatStrings = {dateValue};
1189 CopyUTF8toUTF16(escapedBuffer, *logFormatStrings.AppendElement());
1190 nsString filterLogMessage;
1191 rv = bundle->FormatStringFromName("filterLogLine", logFormatStrings,
1192 filterLogMessage);
1193 NS_ENSURE_SUCCESS(rv, rv);
1194
1195 // Write message into log stream.
1196 uint32_t writeCount;
1197 rv = logStream->Write(LOG_ENTRY_START_TAG, LOG_ENTRY_START_TAG_LEN,
1198 &writeCount);
1199 NS_ENSURE_SUCCESS(rv, rv);
1200 NS_ASSERTION(writeCount == LOG_ENTRY_START_TAG_LEN,
1201 "failed to write out start log tag");
1202
1203 NS_ConvertUTF16toUTF8 buffer(filterLogMessage);
1204 uint32_t escapedBufferLen = buffer.Length();
1205 rv = logStream->Write(buffer.get(), escapedBufferLen, &writeCount);
1206 NS_ENSURE_SUCCESS(rv, rv);
1207 NS_ASSERTION(writeCount == escapedBufferLen, "failed to write out log hit");
1208
1209 rv = logStream->Write(LOG_ENTRY_END_TAG, LOG_ENTRY_END_TAG_LEN, &writeCount);
1210 NS_ENSURE_SUCCESS(rv, rv);
1211 NS_ASSERTION(writeCount == LOG_ENTRY_END_TAG_LEN,
1212 "failed to write out end log tag");
1213 return NS_OK;
1214 }
1215 // ------------ End FilterList methods ------------------
1216