summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/search/src/nsMsgFilterList.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/search/src/nsMsgFilterList.cpp')
-rw-r--r--comm/mailnews/search/src/nsMsgFilterList.cpp1207
1 files changed, 1207 insertions, 0 deletions
diff --git a/comm/mailnews/search/src/nsMsgFilterList.cpp b/comm/mailnews/search/src/nsMsgFilterList.cpp
new file mode 100644
index 0000000000..4a81eafe09
--- /dev/null
+++ b/comm/mailnews/search/src/nsMsgFilterList.cpp
@@ -0,0 +1,1207 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// this file implements the nsMsgFilterList interface
+
+#include "nsTextFormatter.h"
+
+#include "msgCore.h"
+#include "nsMsgFilterList.h"
+#include "nsMsgFilter.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFilterHitNotify.h"
+#include "nsMsgUtils.h"
+#include "nsMsgSearchTerm.h"
+#include "nsString.h"
+#include "nsIMsgFilterService.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMemory.h"
+#include "prmem.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Components.h"
+#include "mozilla/Logging.h"
+#include "mozilla/intl/AppDateTimeFormat.h"
+#include <ctype.h>
+
+// Marker for EOF or failure during read
+#define EOF_CHAR -1
+
+using namespace mozilla;
+
+extern LazyLogModule FILTERLOGMODULE;
+
+static uint32_t nextListId = 0;
+
+nsMsgFilterList::nsMsgFilterList() : m_fileVersion(0) {
+ m_loggingEnabled = false;
+ m_startWritingToBuffer = false;
+ m_temporaryList = false;
+ m_curFilter = nullptr;
+ m_listId.Assign("List");
+ m_listId.AppendInt(nextListId++);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Creating a new filter list with id=%s", m_listId.get()));
+}
+
+NS_IMPL_ADDREF(nsMsgFilterList)
+NS_IMPL_RELEASE(nsMsgFilterList)
+NS_IMPL_QUERY_INTERFACE(nsMsgFilterList, nsIMsgFilterList)
+
+NS_IMETHODIMP nsMsgFilterList::CreateFilter(const nsAString& name,
+ class nsIMsgFilter** aFilter) {
+ NS_ENSURE_ARG_POINTER(aFilter);
+
+ NS_ADDREF(*aFilter = new nsMsgFilter);
+
+ (*aFilter)->SetFilterName(name);
+ (*aFilter)->SetFilterList(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::SetLoggingEnabled(bool enabled) {
+ if (!enabled) {
+ // Disabling logging has side effect of closing logfile (if open).
+ SetLogStream(nullptr);
+ }
+ m_loggingEnabled = enabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::GetLoggingEnabled(bool* enabled) {
+ *enabled = m_loggingEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::GetListId(nsACString& aListId) {
+ aListId.Assign(m_listId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::GetFolder(nsIMsgFolder** aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ NS_IF_ADDREF(*aFolder = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::SetFolder(nsIMsgFolder* aFolder) {
+ m_folder = aFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::SaveToFile(nsIOutputStream* stream) {
+ if (!stream) return NS_ERROR_NULL_POINTER;
+ return SaveTextFilters(stream);
+}
+
+#define LOG_HEADER \
+ "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style " \
+ "type=\"text/css\">body{font-family:Consolas,\"Lucida " \
+ "Console\",Monaco,\"Courier " \
+ "New\",Courier,monospace;font-size:small}</style>\n</head>\n<body>\n"
+#define LOG_HEADER_LEN (strlen(LOG_HEADER))
+
+nsresult nsMsgFilterList::EnsureLogFile(nsIFile* file) {
+ bool exists;
+ nsresult rv = file->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists) {
+ rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ int64_t fileSize;
+ rv = file->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write the header at the start
+ if (fileSize == 0) {
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = MsgGetFileStream(file, getter_AddRefs(outputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t writeCount;
+ rv = outputStream->Write(LOG_HEADER, LOG_HEADER_LEN, &writeCount);
+ NS_ASSERTION(writeCount == LOG_HEADER_LEN,
+ "failed to write out log header");
+ NS_ENSURE_SUCCESS(rv, rv);
+ outputStream->Close();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::ClearLog() {
+ bool loggingEnabled = m_loggingEnabled;
+
+ // disable logging while clearing (and close logStream if open).
+ SetLoggingEnabled(false);
+
+ nsCOMPtr<nsIFile> file;
+ if (NS_SUCCEEDED(GetLogFile(getter_AddRefs(file)))) {
+ file->Remove(false);
+ // Recreate the file, with just the html header.
+ EnsureLogFile(file);
+ }
+
+ SetLoggingEnabled(loggingEnabled);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::GetLogFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ // XXX todo
+ // the path to the log file won't change
+ // should we cache it?
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString type;
+ rv = server->GetType(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isServer = false;
+ rv = m_folder->GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // for news folders (not servers), the filter file is
+ // mcom.test.dat
+ // where the summary file is
+ // mcom.test.msf
+ // since the log is an html file we make it
+ // mcom.test.htm
+ if (type.EqualsLiteral("nntp") && !isServer) {
+ nsCOMPtr<nsIFile> thisFolder;
+ rv = m_folder->GetFilePath(getter_AddRefs(thisFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> filterLogFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filterLogFile->InitWithFile(thisFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // NOTE:
+ // we don't we need to call NS_MsgHashIfNecessary()
+ // it's already been hashed, if necessary
+ nsAutoString filterLogName;
+ rv = filterLogFile->GetLeafName(filterLogName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filterLogName.AppendLiteral(u".htm");
+
+ rv = filterLogFile->SetLeafName(filterLogName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filterLogFile.forget(aFile);
+ } else {
+ rv = server->GetLocalPath(aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = (*aFile)->AppendNative("filterlog.html"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return EnsureLogFile(*aFile);
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::GetLogURL(nsACString& aLogURL) {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetLogFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetURLSpecFromFile(file, aLogURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return !aLogURL.IsEmpty() ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::SetLogStream(nsIOutputStream* aLogStream) {
+ // if there is a log stream already, close it
+ if (m_logStream) {
+ m_logStream->Close(); // will flush
+ }
+
+ m_logStream = aLogStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::GetLogStream(nsIOutputStream** aLogStream) {
+ NS_ENSURE_ARG_POINTER(aLogStream);
+
+ if (!m_logStream && m_loggingEnabled) {
+ nsCOMPtr<nsIFile> logFile;
+ nsresult rv = GetLogFile(getter_AddRefs(logFile));
+ if (NS_SUCCEEDED(rv)) {
+ // Make sure it exists and has it's initial header.
+ rv = EnsureLogFile(logFile);
+ if (NS_SUCCEEDED(rv)) {
+ // append to the end of the log file
+ rv = MsgNewBufferedFileOutputStream(
+ getter_AddRefs(m_logStream), logFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_APPEND, 0666);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ m_logStream = nullptr;
+ }
+ }
+
+ // Always returns NS_OK. The stream can be null.
+ NS_IF_ADDREF(*aLogStream = m_logStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::ApplyFiltersToHdr(nsMsgFilterTypeType filterType,
+ nsIMsgDBHdr* msgHdr, nsIMsgFolder* folder,
+ nsIMsgDatabase* db,
+ const nsACString& headers,
+ nsIMsgFilterHitNotify* listener,
+ nsIMsgWindow* msgWindow) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
+ ("(Auto) nsMsgFilterList::ApplyFiltersToHdr"));
+ if (!msgHdr) {
+ // Sometimes we get here with no header, so let's not crash on that
+ // later on.
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
+ ("(Auto) Called with NULL message header, nothing to do"));
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsCOMPtr<nsIMsgFilter> filter;
+ uint32_t filterCount = 0;
+ nsresult rv = GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsMsgSearchScopeTerm> scope =
+ new nsMsgSearchScopeTerm(nullptr, nsMsgSearchScope::offlineMail, folder);
+
+ nsString folderName;
+ folder->GetName(folderName);
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ nsCString typeName;
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService("@mozilla.org/messenger/services/filters;1", &rv);
+ filterService->FilterTypeName(filterType, typeName);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Auto) Filter run initiated, trigger=%s (%i)", typeName.get(),
+ filterType));
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Auto) Running %" PRIu32
+ " filters from %s on message with key %" PRIu32 " in folder '%s'",
+ filterCount, m_listId.get(), msgKeyToInt(msgKey),
+ NS_ConvertUTF16toUTF8(folderName).get()));
+
+ for (uint32_t filterIndex = 0; filterIndex < filterCount; filterIndex++) {
+ if (NS_SUCCEEDED(GetFilterAt(filterIndex, getter_AddRefs(filter)))) {
+ bool isEnabled;
+ nsMsgFilterTypeType curFilterType;
+
+ filter->GetEnabled(&isEnabled);
+ if (!isEnabled) {
+ // clang-format off
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Auto) Skipping disabled filter at index %" PRIu32,
+ filterIndex));
+ // clang-format on
+ continue;
+ }
+
+ nsString filterName;
+ filter->GetFilterName(filterName);
+ filter->GetFilterType(&curFilterType);
+ if (curFilterType & filterType) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Auto) Running filter %" PRIu32, filterIndex));
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
+ ("(Auto) Filter name: %s",
+ NS_ConvertUTF16toUTF8(filterName).get()));
+
+ nsresult matchTermStatus = NS_OK;
+ bool result = false;
+
+ filter->SetScope(scope);
+ matchTermStatus =
+ filter->MatchHdr(msgHdr, folder, db, headers, &result);
+ filter->SetScope(nullptr);
+ if (NS_SUCCEEDED(matchTermStatus) && result && listener) {
+ nsCString msgId;
+ msgHdr->GetMessageId(getter_Copies(msgId));
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Auto) Filter matched message with key %" PRIu32,
+ msgKeyToInt(msgKey)));
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
+ ("(Auto) Matched message ID: %s", msgId.get()));
+
+ bool applyMore = true;
+ rv = listener->ApplyFilterHit(filter, msgWindow, &applyMore);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Auto) Applying filter actions failed"));
+ LogFilterMessage(u"Applying filter actions failed"_ns, filter);
+ } else {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Auto) Applying filter actions succeeded"));
+ }
+ if (NS_FAILED(rv) || !applyMore) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Auto) Stopping further filter execution"
+ " on this message"));
+ break;
+ }
+ } else {
+ if (NS_FAILED(matchTermStatus)) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Auto) Filter evaluation failed"));
+ LogFilterMessage(u"Filter evaluation failed"_ns, filter);
+ }
+ if (!result)
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Auto) Filter didn't match"));
+ }
+ } else {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Auto) Skipping filter of non-matching type"
+ " at index %" PRIu32,
+ filterIndex));
+ }
+ }
+ }
+ if (NS_FAILED(rv)) {
+ // clang-format off
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Auto) Filter run failed (%" PRIx32 ")", static_cast<uint32_t>(rv)));
+ // clang-format on
+ LogFilterMessage(u"Filter run failed"_ns, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::SetDefaultFile(nsIFile* aFile) {
+ m_defaultFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::GetDefaultFile(nsIFile** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ NS_IF_ADDREF(*aResult = m_defaultFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::SaveToDefaultFile() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService("@mozilla.org/messenger/services/filters;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return filterService->SaveFilterList(this, m_defaultFile);
+}
+
+typedef struct {
+ nsMsgFilterFileAttribValue attrib;
+ const char* attribName;
+} FilterFileAttribEntry;
+
+static FilterFileAttribEntry FilterFileAttribTable[] = {
+ {nsIMsgFilterList::attribNone, ""},
+ {nsIMsgFilterList::attribVersion, "version"},
+ {nsIMsgFilterList::attribLogging, "logging"},
+ {nsIMsgFilterList::attribName, "name"},
+ {nsIMsgFilterList::attribEnabled, "enabled"},
+ {nsIMsgFilterList::attribDescription, "description"},
+ {nsIMsgFilterList::attribType, "type"},
+ {nsIMsgFilterList::attribScriptFile, "scriptName"},
+ {nsIMsgFilterList::attribAction, "action"},
+ {nsIMsgFilterList::attribActionValue, "actionValue"},
+ {nsIMsgFilterList::attribCondition, "condition"},
+ {nsIMsgFilterList::attribCustomId, "customId"},
+};
+
+static const unsigned int sNumFilterFileAttribTable =
+ MOZ_ARRAY_LENGTH(FilterFileAttribTable);
+
+// If we want to buffer file IO, wrap it in here.
+int nsMsgFilterList::ReadChar(nsIInputStream* aStream) {
+ char newChar;
+ uint32_t bytesRead;
+ uint64_t bytesAvailable;
+ nsresult rv = aStream->Available(&bytesAvailable);
+ if (NS_FAILED(rv) || bytesAvailable == 0) return EOF_CHAR;
+
+ rv = aStream->Read(&newChar, 1, &bytesRead);
+ if (NS_FAILED(rv) || !bytesRead) return EOF_CHAR;
+
+ if (m_startWritingToBuffer) m_unparsedFilterBuffer.Append(newChar);
+ return (unsigned char)newChar; // Make sure the char is unsigned.
+}
+
+int nsMsgFilterList::SkipWhitespace(nsIInputStream* aStream) {
+ int ch;
+ do {
+ ch = ReadChar(aStream);
+ } while (!(ch & 0x80) &&
+ isspace(ch)); // isspace can crash with non-ascii input
+
+ return ch;
+}
+
+bool nsMsgFilterList::StrToBool(nsCString& str) {
+ return str.EqualsLiteral("yes");
+}
+
+int nsMsgFilterList::LoadAttrib(nsMsgFilterFileAttribValue& attrib,
+ nsIInputStream* aStream) {
+ char attribStr[100];
+ int curChar;
+ attrib = nsIMsgFilterList::attribNone;
+
+ curChar = SkipWhitespace(aStream);
+ int i;
+ for (i = 0; i + 1 < (int)(sizeof(attribStr));) {
+ if (curChar == EOF_CHAR || (!(curChar & 0x80) && isspace(curChar)) ||
+ curChar == '=')
+ break;
+ attribStr[i++] = curChar;
+ curChar = ReadChar(aStream);
+ }
+ attribStr[i] = '\0';
+ for (unsigned int tableIndex = 0; tableIndex < sNumFilterFileAttribTable;
+ tableIndex++) {
+ if (!PL_strcasecmp(attribStr,
+ FilterFileAttribTable[tableIndex].attribName)) {
+ attrib = FilterFileAttribTable[tableIndex].attrib;
+ break;
+ }
+ }
+ return curChar;
+}
+
+const char* nsMsgFilterList::GetStringForAttrib(
+ nsMsgFilterFileAttribValue attrib) {
+ for (unsigned int tableIndex = 0; tableIndex < sNumFilterFileAttribTable;
+ tableIndex++) {
+ if (attrib == FilterFileAttribTable[tableIndex].attrib)
+ return FilterFileAttribTable[tableIndex].attribName;
+ }
+ return nullptr;
+}
+
+nsresult nsMsgFilterList::LoadValue(nsCString& value, nsIInputStream* aStream) {
+ nsAutoCString valueStr;
+ int curChar;
+ value = "";
+ curChar = SkipWhitespace(aStream);
+ if (curChar != '"') {
+ NS_ASSERTION(false, "expecting quote as start of value");
+ return NS_MSG_FILTER_PARSE_ERROR;
+ }
+ curChar = ReadChar(aStream);
+ do {
+ if (curChar == '\\') {
+ int nextChar = ReadChar(aStream);
+ if (nextChar == '"')
+ curChar = '"';
+ else if (nextChar == '\\') // replace "\\" with "\"
+ {
+ valueStr += curChar;
+ curChar = ReadChar(aStream);
+ } else {
+ valueStr += curChar;
+ curChar = nextChar;
+ }
+ } else {
+ if (curChar == EOF_CHAR || curChar == '"' || curChar == '\n' ||
+ curChar == '\r') {
+ value += valueStr;
+ break;
+ }
+ }
+ valueStr += curChar;
+ curChar = ReadChar(aStream);
+ } while (curChar != EOF_CHAR);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::LoadTextFilters(
+ already_AddRefed<nsIInputStream> aStream) {
+ nsresult err = NS_OK;
+ uint64_t bytesAvailable;
+
+ nsCOMPtr<nsIInputStream> bufStream;
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+ err = NS_NewBufferedInputStream(getter_AddRefs(bufStream), stream.forget(),
+ FILE_IO_BUFFER_SIZE);
+ NS_ENSURE_SUCCESS(err, err);
+
+ nsMsgFilterFileAttribValue attrib;
+ nsCOMPtr<nsIMsgRuleAction> currentFilterAction;
+ // We'd really like to move lot's of these into the objects that they refer
+ // to.
+ do {
+ nsAutoCString value;
+ nsresult intToStringResult;
+
+ int curChar;
+ curChar = LoadAttrib(attrib, bufStream);
+ if (curChar == EOF_CHAR) // reached eof
+ break;
+ err = LoadValue(value, bufStream);
+ if (NS_FAILED(err)) break;
+
+ switch (attrib) {
+ case nsIMsgFilterList::attribNone:
+ if (m_curFilter) m_curFilter->SetUnparseable(true);
+ break;
+ case nsIMsgFilterList::attribVersion:
+ m_fileVersion = value.ToInteger(&intToStringResult);
+ if (NS_FAILED(intToStringResult)) {
+ attrib = nsIMsgFilterList::attribNone;
+ NS_ASSERTION(false, "error parsing filter file version");
+ }
+ break;
+ case nsIMsgFilterList::attribLogging:
+ m_loggingEnabled = StrToBool(value);
+ // We are going to buffer each filter as we read them.
+ // Make sure no garbage is there
+ m_unparsedFilterBuffer.Truncate();
+ m_startWritingToBuffer = true; // filters begin now
+ break;
+ case nsIMsgFilterList::attribName: // every filter starts w/ a name
+ {
+ if (m_curFilter) {
+ int32_t nextFilterStartPos = m_unparsedFilterBuffer.RFind("name");
+
+ nsAutoCString nextFilterPart;
+ nextFilterPart = Substring(m_unparsedFilterBuffer, nextFilterStartPos,
+ m_unparsedFilterBuffer.Length());
+ m_unparsedFilterBuffer.SetLength(nextFilterStartPos);
+
+ bool unparseableFilter;
+ m_curFilter->GetUnparseable(&unparseableFilter);
+ if (unparseableFilter) {
+ m_curFilter->SetUnparsedBuffer(m_unparsedFilterBuffer);
+ m_curFilter->SetEnabled(false); // disable the filter because we
+ // don't know how to apply it
+ }
+ m_unparsedFilterBuffer = nextFilterPart;
+ }
+ nsMsgFilter* filter = new nsMsgFilter;
+ if (filter == nullptr) {
+ err = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+ filter->SetFilterList(static_cast<nsIMsgFilterList*>(this));
+ nsAutoString unicodeStr;
+ if (m_fileVersion == k45Version) {
+ NS_CopyNativeToUnicode(value, unicodeStr);
+ filter->SetFilterName(unicodeStr);
+ } else {
+ CopyUTF8toUTF16(value, unicodeStr);
+ filter->SetFilterName(unicodeStr);
+ }
+ m_curFilter = filter;
+ m_filters.AppendElement(filter);
+ } break;
+ case nsIMsgFilterList::attribEnabled:
+ if (m_curFilter) m_curFilter->SetEnabled(StrToBool(value));
+ break;
+ case nsIMsgFilterList::attribDescription:
+ if (m_curFilter) m_curFilter->SetFilterDesc(value);
+ break;
+ case nsIMsgFilterList::attribType:
+ if (m_curFilter) {
+ // Older versions of filters didn't have the ability to turn on/off
+ // the manual filter context, so default manual to be on in that case
+ int32_t filterType = value.ToInteger(&intToStringResult);
+ if (m_fileVersion < kManualContextVersion)
+ filterType |= nsMsgFilterType::Manual;
+ m_curFilter->SetType((nsMsgFilterTypeType)filterType);
+ }
+ break;
+ case nsIMsgFilterList::attribScriptFile:
+ if (m_curFilter) m_curFilter->SetFilterScript(&value);
+ break;
+ case nsIMsgFilterList::attribAction:
+ if (m_curFilter) {
+ nsMsgRuleActionType actionType =
+ nsMsgFilter::GetActionForFilingStr(value);
+ if (actionType == nsMsgFilterAction::None)
+ m_curFilter->SetUnparseable(true);
+ else {
+ err =
+ m_curFilter->CreateAction(getter_AddRefs(currentFilterAction));
+ NS_ENSURE_SUCCESS(err, err);
+ currentFilterAction->SetType(actionType);
+ m_curFilter->AppendAction(currentFilterAction);
+ }
+ }
+ break;
+ case nsIMsgFilterList::attribActionValue:
+ if (m_curFilter && currentFilterAction) {
+ nsMsgRuleActionType type;
+ currentFilterAction->GetType(&type);
+ if (type == nsMsgFilterAction::MoveToFolder ||
+ type == nsMsgFilterAction::CopyToFolder)
+ err = m_curFilter->ConvertMoveOrCopyToFolderValue(
+ currentFilterAction, value);
+ else if (type == nsMsgFilterAction::ChangePriority) {
+ nsMsgPriorityValue outPriority;
+ nsresult res =
+ NS_MsgGetPriorityFromString(value.get(), outPriority);
+ if (NS_SUCCEEDED(res))
+ currentFilterAction->SetPriority(outPriority);
+ else
+ NS_ASSERTION(false, "invalid priority in filter file");
+ } else if (type == nsMsgFilterAction::JunkScore) {
+ nsresult res;
+ int32_t junkScore = value.ToInteger(&res);
+ if (NS_SUCCEEDED(res)) currentFilterAction->SetJunkScore(junkScore);
+ } else if (type == nsMsgFilterAction::Forward ||
+ type == nsMsgFilterAction::Reply ||
+ type == nsMsgFilterAction::AddTag ||
+ type == nsMsgFilterAction::Custom) {
+ currentFilterAction->SetStrValue(value);
+ }
+ }
+ break;
+ case nsIMsgFilterList::attribCondition:
+ if (m_curFilter) {
+ if (m_fileVersion == k45Version) {
+ nsAutoString unicodeStr;
+ NS_CopyNativeToUnicode(value, unicodeStr);
+ CopyUTF16toUTF8(unicodeStr, value);
+ }
+ err = ParseCondition(m_curFilter, value.get());
+ if (err == NS_ERROR_INVALID_ARG)
+ err = m_curFilter->SetUnparseable(true);
+ NS_ENSURE_SUCCESS(err, err);
+ }
+ break;
+ case nsIMsgFilterList::attribCustomId:
+ if (m_curFilter && currentFilterAction) {
+ err = currentFilterAction->SetCustomId(value);
+ NS_ENSURE_SUCCESS(err, err);
+ }
+ break;
+ }
+ } while (NS_SUCCEEDED(bufStream->Available(&bytesAvailable)));
+
+ if (m_curFilter) {
+ bool unparseableFilter;
+ m_curFilter->GetUnparseable(&unparseableFilter);
+ if (unparseableFilter) {
+ m_curFilter->SetUnparsedBuffer(m_unparsedFilterBuffer);
+ m_curFilter->SetEnabled(
+ false); // disable the filter because we don't know how to apply it
+ }
+ }
+
+ return err;
+}
+
+// parse condition like "(subject, contains, fred) AND (body, isn't, "foo)")"
+// values with close parens will be quoted.
+// what about values with close parens and quotes? e.g., (body, isn't, "foo")")
+// I guess interior quotes will need to be escaped - ("foo\")")
+// which will get written out as (\"foo\\")\") and read in as ("foo\")"
+// ALL means match all messages.
+NS_IMETHODIMP nsMsgFilterList::ParseCondition(nsIMsgFilter* aFilter,
+ const char* aCondition) {
+ NS_ENSURE_ARG_POINTER(aFilter);
+
+ bool done = false;
+ nsresult err = NS_OK;
+ const char* curPtr = aCondition;
+ if (!strcmp(aCondition, "ALL")) {
+ RefPtr<nsMsgSearchTerm> newTerm = new nsMsgSearchTerm;
+ newTerm->m_matchAll = true;
+ aFilter->AppendTerm(newTerm);
+ return NS_OK;
+ }
+
+ while (!done) {
+ // insert code to save the boolean operator if there is one for this search
+ // term....
+ const char* openParen = PL_strchr(curPtr, '(');
+ const char* orTermPos = PL_strchr(
+ curPtr, 'O'); // determine if an "OR" appears b4 the openParen...
+ bool ANDTerm = true;
+ if (orTermPos &&
+ orTermPos < openParen) // make sure OR term falls before the '('
+ ANDTerm = false;
+
+ char* termDup = nullptr;
+ if (openParen) {
+ bool foundEndTerm = false;
+ bool inQuote = false;
+ for (curPtr = openParen + 1; *curPtr; curPtr++) {
+ if (*curPtr == '\\' && *(curPtr + 1) == '"')
+ curPtr++;
+ else if (*curPtr == ')' && !inQuote) {
+ foundEndTerm = true;
+ break;
+ } else if (*curPtr == '"')
+ inQuote = !inQuote;
+ }
+ if (foundEndTerm) {
+ int termLen = curPtr - openParen - 1;
+ termDup = (char*)PR_Malloc(termLen + 1);
+ if (termDup) {
+ PL_strncpy(termDup, openParen + 1, termLen + 1);
+ termDup[termLen] = '\0';
+ } else {
+ err = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+ }
+ } else
+ break;
+ if (termDup) {
+ RefPtr<nsMsgSearchTerm> newTerm = new nsMsgSearchTerm;
+ // Invert nsMsgSearchTerm::EscapeQuotesInStr()
+ for (char *to = termDup, *from = termDup;;) {
+ if (*from == '\\' && from[1] == '"') from++;
+ if (!(*to++ = *from++)) break;
+ }
+ newTerm->m_booleanOp = (ANDTerm) ? nsMsgSearchBooleanOp::BooleanAND
+ : nsMsgSearchBooleanOp::BooleanOR;
+
+ err = newTerm->DeStreamNew(termDup, PL_strlen(termDup));
+ NS_ENSURE_SUCCESS(err, err);
+ aFilter->AppendTerm(newTerm);
+ PR_FREEIF(termDup);
+ } else
+ break;
+ }
+ return err;
+}
+
+nsresult nsMsgFilterList::WriteIntAttr(nsMsgFilterFileAttribValue attrib,
+ int value, nsIOutputStream* aStream) {
+ nsresult rv = NS_OK;
+ const char* attribStr = GetStringForAttrib(attrib);
+ if (attribStr) {
+ uint32_t bytesWritten;
+ nsAutoCString writeStr(attribStr);
+ writeStr.AppendLiteral("=\"");
+ writeStr.AppendInt(value);
+ writeStr.AppendLiteral("\"" MSG_LINEBREAK);
+ rv = aStream->Write(writeStr.get(), writeStr.Length(), &bytesWritten);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::WriteStrAttr(nsMsgFilterFileAttribValue attrib,
+ const char* aStr, nsIOutputStream* aStream) {
+ nsresult rv = NS_OK;
+ if (aStr && *aStr &&
+ aStream) // only proceed if we actually have a string to write out.
+ {
+ char* escapedStr = nullptr;
+ if (PL_strchr(aStr, '"'))
+ escapedStr = nsMsgSearchTerm::EscapeQuotesInStr(aStr);
+
+ const char* attribStr = GetStringForAttrib(attrib);
+ if (attribStr) {
+ uint32_t bytesWritten;
+ nsAutoCString writeStr(attribStr);
+ writeStr.AppendLiteral("=\"");
+ writeStr.Append((escapedStr) ? escapedStr : aStr);
+ writeStr.AppendLiteral("\"" MSG_LINEBREAK);
+ rv = aStream->Write(writeStr.get(), writeStr.Length(), &bytesWritten);
+ }
+ PR_Free(escapedStr);
+ }
+ return rv;
+}
+
+nsresult nsMsgFilterList::WriteBoolAttr(nsMsgFilterFileAttribValue attrib,
+ bool boolVal,
+ nsIOutputStream* aStream) {
+ return WriteStrAttr(attrib, (boolVal) ? "yes" : "no", aStream);
+}
+
+nsresult nsMsgFilterList::WriteWstrAttr(nsMsgFilterFileAttribValue attrib,
+ const char16_t* aFilterName,
+ nsIOutputStream* aStream) {
+ WriteStrAttr(attrib, NS_ConvertUTF16toUTF8(aFilterName).get(), aStream);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::SaveTextFilters(nsIOutputStream* aStream) {
+ uint32_t filterCount = 0;
+ nsresult err = GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = WriteIntAttr(nsIMsgFilterList::attribVersion, kFileVersion, aStream);
+ NS_ENSURE_SUCCESS(err, err);
+ err =
+ WriteBoolAttr(nsIMsgFilterList::attribLogging, m_loggingEnabled, aStream);
+ NS_ENSURE_SUCCESS(err, err);
+ for (uint32_t i = 0; i < filterCount; i++) {
+ nsCOMPtr<nsIMsgFilter> filter;
+ if (NS_SUCCEEDED(GetFilterAt(i, getter_AddRefs(filter))) && filter) {
+ filter->SetFilterList(this);
+
+ // if the filter is temporary, don't write it to disk
+ bool isTemporary;
+ err = filter->GetTemporary(&isTemporary);
+ if (NS_SUCCEEDED(err) && !isTemporary) {
+ err = filter->SaveToTextFile(aStream);
+ if (NS_FAILED(err)) break;
+ }
+ } else
+ break;
+ }
+ if (NS_SUCCEEDED(err)) m_arbitraryHeaders.Truncate();
+ return err;
+}
+
+nsMsgFilterList::~nsMsgFilterList() {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Closing filter list %s", m_listId.get()));
+}
+
+nsresult nsMsgFilterList::Close() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+nsresult nsMsgFilterList::GetFilterCount(uint32_t* pCount) {
+ NS_ENSURE_ARG_POINTER(pCount);
+
+ *pCount = m_filters.Length();
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::GetFilterAt(uint32_t filterIndex,
+ nsIMsgFilter** filter) {
+ NS_ENSURE_ARG_POINTER(filter);
+
+ uint32_t filterCount = 0;
+ GetFilterCount(&filterCount);
+ NS_ENSURE_ARG(filterIndex < filterCount);
+
+ NS_IF_ADDREF(*filter = m_filters[filterIndex]);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::GetFilterNamed(const nsAString& aName,
+ nsIMsgFilter** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ uint32_t count = 0;
+ nsresult rv = GetFilterCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = nullptr;
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIMsgFilter> filter;
+ rv = GetFilterAt(i, getter_AddRefs(filter));
+ if (NS_FAILED(rv)) continue;
+
+ nsString filterName;
+ filter->GetFilterName(filterName);
+ if (filterName.Equals(aName)) {
+ filter.forget(aResult);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::SetFilterAt(uint32_t filterIndex,
+ nsIMsgFilter* filter) {
+ m_filters[filterIndex] = filter;
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::RemoveFilterAt(uint32_t filterIndex) {
+ m_filters.RemoveElementAt(filterIndex);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::RemoveFilter(nsIMsgFilter* aFilter) {
+ m_filters.RemoveElement(aFilter);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::InsertFilterAt(uint32_t filterIndex,
+ nsIMsgFilter* aFilter) {
+ if (!m_temporaryList) aFilter->SetFilterList(this);
+ m_filters.InsertElementAt(filterIndex, aFilter);
+
+ return NS_OK;
+}
+
+// Attempt to move the filter at index filterIndex in the specified direction.
+// If motion not possible in that direction, we still return success.
+// We could return an error if the FE's want to beep or something.
+nsresult nsMsgFilterList::MoveFilterAt(uint32_t filterIndex,
+ nsMsgFilterMotionValue motion) {
+ NS_ENSURE_ARG((motion == nsMsgFilterMotion::up) ||
+ (motion == nsMsgFilterMotion::down));
+
+ uint32_t filterCount = 0;
+ nsresult rv = GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_ARG(filterIndex < filterCount);
+
+ uint32_t newIndex = filterIndex;
+
+ if (motion == nsMsgFilterMotion::up) {
+ // are we already at the top?
+ if (filterIndex == 0) return NS_OK;
+
+ newIndex = filterIndex - 1;
+ } else if (motion == nsMsgFilterMotion::down) {
+ // are we already at the bottom?
+ if (filterIndex == filterCount - 1) return NS_OK;
+
+ newIndex = filterIndex + 1;
+ }
+
+ nsCOMPtr<nsIMsgFilter> tempFilter1;
+ rv = GetFilterAt(newIndex, getter_AddRefs(tempFilter1));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFilter> tempFilter2;
+ rv = GetFilterAt(filterIndex, getter_AddRefs(tempFilter2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetFilterAt(newIndex, tempFilter2);
+ SetFilterAt(filterIndex, tempFilter1);
+
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::MoveFilter(nsIMsgFilter* aFilter,
+ nsMsgFilterMotionValue motion) {
+ size_t filterIndex = m_filters.IndexOf(aFilter, 0);
+ NS_ENSURE_ARG(filterIndex != m_filters.NoIndex);
+
+ return MoveFilterAt(filterIndex, motion);
+}
+
+nsresult nsMsgFilterList::GetVersion(int16_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_fileVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::MatchOrChangeFilterTarget(
+ const nsACString& oldFolderUri, const nsACString& newFolderUri,
+ bool caseInsensitive, bool* found) {
+ NS_ENSURE_ARG_POINTER(found);
+
+ uint32_t numFilters = 0;
+ nsresult rv = GetFilterCount(&numFilters);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFilter> filter;
+ nsCString folderUri;
+ *found = false;
+ for (uint32_t index = 0; index < numFilters; index++) {
+ rv = GetFilterAt(index, getter_AddRefs(filter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numActions;
+ rv = filter->GetActionCount(&numActions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++) {
+ nsCOMPtr<nsIMsgRuleAction> filterAction;
+ rv = filter->GetActionAt(actionIndex, getter_AddRefs(filterAction));
+ if (NS_FAILED(rv) || !filterAction) continue;
+
+ nsMsgRuleActionType actionType;
+ if (NS_FAILED(filterAction->GetType(&actionType))) continue;
+
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder) {
+ rv = filterAction->GetTargetFolderUri(folderUri);
+ if (NS_SUCCEEDED(rv) && !folderUri.IsEmpty()) {
+ bool matchFound = false;
+ if (caseInsensitive) {
+ if (folderUri.Equals(oldFolderUri,
+ nsCaseInsensitiveCStringComparator)) // local
+ matchFound = true;
+ } else {
+ if (folderUri.Equals(oldFolderUri)) // imap
+ matchFound = true;
+ }
+ if (matchFound) {
+ *found = true;
+ // if we just want to match the uri's, newFolderUri will be null
+ if (!newFolderUri.IsEmpty()) {
+ rv = filterAction->SetTargetFolderUri(newFolderUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+// this would only return true if any filter was on "any header", which we
+// don't support in 6.x
+NS_IMETHODIMP nsMsgFilterList::GetShouldDownloadAllHeaders(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+ return NS_OK;
+}
+
+// leaves m_arbitraryHeaders filed in with the arbitrary headers.
+nsresult nsMsgFilterList::ComputeArbitraryHeaders() {
+ NS_ENSURE_TRUE(m_arbitraryHeaders.IsEmpty(), NS_OK);
+
+ uint32_t numFilters = 0;
+ nsresult rv = GetFilterCount(&numFilters);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFilter> filter;
+ nsMsgSearchAttribValue attrib;
+ nsCString arbitraryHeader;
+ for (uint32_t index = 0; index < numFilters; index++) {
+ rv = GetFilterAt(index, getter_AddRefs(filter));
+ if (!(NS_SUCCEEDED(rv) && filter)) continue;
+
+ nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms;
+ filter->GetSearchTerms(searchTerms);
+ for (uint32_t i = 0; i < searchTerms.Length(); i++) {
+ filter->GetTerm(i, &attrib, nullptr, nullptr, nullptr, arbitraryHeader);
+ if (!arbitraryHeader.IsEmpty()) {
+ if (m_arbitraryHeaders.IsEmpty())
+ m_arbitraryHeaders.Assign(arbitraryHeader);
+ else if (!FindInReadable(arbitraryHeader, m_arbitraryHeaders,
+ nsCaseInsensitiveCStringComparator)) {
+ m_arbitraryHeaders.Append(' ');
+ m_arbitraryHeaders.Append(arbitraryHeader);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::GetArbitraryHeaders(nsACString& aResult) {
+ ComputeArbitraryHeaders();
+ aResult = m_arbitraryHeaders;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::FlushLogIfNecessary() {
+ // only flush the log if we are logging
+ if (m_loggingEnabled && m_logStream) {
+ nsresult rv = m_logStream->Flush();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+#define LOG_ENTRY_START_TAG "<p>\n"
+#define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG))
+#define LOG_ENTRY_END_TAG "</p>\n"
+#define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG))
+
+NS_IMETHODIMP nsMsgFilterList::LogFilterMessage(const nsAString& message,
+ nsIMsgFilter* filter) {
+ if (!m_loggingEnabled) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIOutputStream> logStream;
+ GetLogStream(getter_AddRefs(logStream));
+ if (!logStream) {
+ // Logging is on, but we failed to access the filter logfile.
+ // For completeness, we'll return an error, but we don't expect anyone
+ // to ever check it - logging failures shouldn't stop anything else.
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/filter.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString tempMessage(message);
+
+ if (filter) {
+ // If a filter was passed, prepend its name in the log message.
+ nsString filterName;
+ filter->GetFilterName(filterName);
+
+ AutoTArray<nsString, 2> logFormatStrings = {filterName, tempMessage};
+ nsString statusLogMessage;
+ rv = bundle->FormatStringFromName("filterMessage", logFormatStrings,
+ statusLogMessage);
+ if (NS_SUCCEEDED(rv)) tempMessage.Assign(statusLogMessage);
+ }
+
+ // Prepare timestamp
+ PRExplodedTime exploded;
+ nsString dateValue;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ mozilla::intl::DateTimeFormat::StyleBag style;
+ style.date = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short);
+ style.time = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long);
+ mozilla::intl::AppDateTimeFormat::Format(style, &exploded, dateValue);
+
+ // HTML-escape the log for security reasons.
+ // We don't want someone to send us a message with a subject with
+ // HTML tags, especially <script>.
+ nsCString escapedBuffer;
+ nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(tempMessage), escapedBuffer);
+
+ // Print timestamp and the message.
+ AutoTArray<nsString, 2> logFormatStrings = {dateValue};
+ CopyUTF8toUTF16(escapedBuffer, *logFormatStrings.AppendElement());
+ nsString filterLogMessage;
+ rv = bundle->FormatStringFromName("filterLogLine", logFormatStrings,
+ filterLogMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Write message into log stream.
+ uint32_t writeCount;
+ rv = logStream->Write(LOG_ENTRY_START_TAG, LOG_ENTRY_START_TAG_LEN,
+ &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(writeCount == LOG_ENTRY_START_TAG_LEN,
+ "failed to write out start log tag");
+
+ NS_ConvertUTF16toUTF8 buffer(filterLogMessage);
+ uint32_t escapedBufferLen = buffer.Length();
+ rv = logStream->Write(buffer.get(), escapedBufferLen, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(writeCount == escapedBufferLen, "failed to write out log hit");
+
+ rv = logStream->Write(LOG_ENTRY_END_TAG, LOG_ENTRY_END_TAG_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(writeCount == LOG_ENTRY_END_TAG_LEN,
+ "failed to write out end log tag");
+ return NS_OK;
+}
+// ------------ End FilterList methods ------------------