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