summaryrefslogtreecommitdiffstats
path: root/mailnews/base/search/src/nsMsgFilterList.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/search/src/nsMsgFilterList.cpp')
-rw-r--r--mailnews/base/search/src/nsMsgFilterList.cpp1198
1 files changed, 1198 insertions, 0 deletions
diff --git a/mailnews/base/search/src/nsMsgFilterList.cpp b/mailnews/base/search/src/nsMsgFilterList.cpp
new file mode 100644
index 000000000..d5b93fdc2
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilterList.cpp
@@ -0,0 +1,1198 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsIMsgFilterHitNotify.h"
+#include "nsMsgUtils.h"
+#include "nsMsgSearchTerm.h"
+#include "nsStringGlue.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgFilterService.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsMsgI18N.h"
+#include "nsMemory.h"
+#include "prmem.h"
+#include "mozilla/ArrayUtils.h"
+#include <ctype.h>
+
+// unicode "%s" format string
+static const char16_t unicodeFormatter[] = {
+ (char16_t)'%',
+ (char16_t)'s',
+ (char16_t)0,
+};
+
+// Marker for EOF or failure during read
+#define EOF_CHAR -1
+
+nsMsgFilterList::nsMsgFilterList() :
+ m_fileVersion(0)
+{
+ m_loggingEnabled = false;
+ m_startWritingToBuffer = false;
+ m_temporaryList = false;
+ m_curFilter = nullptr;
+}
+
+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);
+
+ nsMsgFilter *filter = new nsMsgFilter;
+ NS_ENSURE_TRUE(filter, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ADDREF(*aFilter = filter);
+
+ filter->SetFilterName(name);
+ filter->SetFilterList(this);
+
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsMsgFilterList, LoggingEnabled, bool, m_loggingEnabled)
+
+NS_IMETHODIMP nsMsgFilterList::GetFolder(nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ *aFolder = m_folder;
+ NS_IF_ADDREF(*aFolder);
+ 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;
+}
+
+nsresult nsMsgFilterList::TruncateLog()
+{
+ // This will flush and close the stream.
+ nsresult rv = SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIFile> file;
+ rv = GetLogFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ file->Remove(false);
+
+ return EnsureLogFile(file);
+}
+
+NS_IMETHODIMP nsMsgFilterList::ClearLog()
+{
+ bool loggingEnabled = m_loggingEnabled;
+
+ // disable logging while clearing
+ m_loggingEnabled = false;
+
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ TruncateLog();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to truncate filter log");
+
+ m_loggingEnabled = 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 <nsIMsgFolder> folder;
+ nsresult rv = GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = 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 = 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.Equals("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.Append(NS_LITERAL_STRING(".htm"));
+
+ rv = filterLogFile->SetLeafName(filterLogName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*aFile = filterLogFile);
+ }
+ else {
+ rv = server->GetLocalPath(aFile);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = (*aFile)->AppendNative(NS_LITERAL_CSTRING("filterlog.html"));
+ 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) {
+ // will flush
+ nsresult rv = m_logStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ m_logStream = aLogStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::GetLogStream(nsIOutputStream **aLogStream)
+{
+ NS_ENSURE_ARG_POINTER(aLogStream);
+
+ nsresult rv;
+
+ if (!m_logStream) {
+ nsCOMPtr <nsIFile> logFile;
+ rv = GetLogFile(getter_AddRefs(logFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // append to the end of the log file
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_logStream),
+ logFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_APPEND,
+ 0666);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!m_logStream)
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ADDREF(*aLogStream = m_logStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::ApplyFiltersToHdr(nsMsgFilterTypeType filterType,
+ nsIMsgDBHdr *msgHdr,
+ nsIMsgFolder *folder,
+ nsIMsgDatabase *db,
+ const char*headers,
+ uint32_t headersSize,
+ nsIMsgFilterHitNotify *listener,
+ nsIMsgWindow *msgWindow)
+{
+ nsCOMPtr<nsIMsgFilter> filter;
+ uint32_t filterCount = 0;
+ nsresult rv = GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgSearchScopeTerm* scope = new nsMsgSearchScopeTerm(nullptr, nsMsgSearchScope::offlineMail, folder);
+ scope->AddRef();
+ if (!scope) return NS_ERROR_OUT_OF_MEMORY;
+
+ 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)
+ continue;
+
+ filter->GetFilterType(&curFilterType);
+ if (curFilterType & filterType)
+ {
+ nsresult matchTermStatus = NS_OK;
+ bool result;
+
+ filter->SetScope(scope);
+ matchTermStatus = filter->MatchHdr(msgHdr, folder, db, headers, headersSize, &result);
+ filter->SetScope(nullptr);
+ if (NS_SUCCEEDED(matchTermStatus) && result && listener)
+ {
+ bool applyMore = true;
+
+ rv = listener->ApplyFilterHit(filter, msgWindow, &applyMore);
+ if (NS_FAILED(rv) || !applyMore)
+ break;
+ }
+ }
+ }
+ }
+ scope->Release();
+ 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(NS_MSGFILTERSERVICE_CONTRACTID, &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;
+ nsresult rv = aStream->Read(&newChar, 1, &bytesRead);
+ if (NS_FAILED(rv) || !bytesRead)
+ return EOF_CHAR;
+ uint64_t bytesAvailable;
+ rv = aStream->Available(&bytesAvailable);
+ if (NS_FAILED(rv))
+ return EOF_CHAR;
+ else
+ {
+ 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.Equals("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(nsIInputStream *aStream)
+{
+ nsresult err = NS_OK;
+ uint64_t bytesAvailable;
+
+ nsCOMPtr<nsIInputStream> bufStream;
+ err = NS_NewBufferedInputStream(getter_AddRefs(bufStream), aStream, 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);
+ m_unparsedFilterBuffer.Truncate(); //we are going to buffer each filter as we read them, make sure no garbage is there
+ 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));
+ if (m_fileVersion == k45Version)
+ {
+ nsAutoString unicodeStr;
+ err = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ value, unicodeStr);
+ if (NS_FAILED(err))
+ break;
+
+ filter->SetFilterName(unicodeStr);
+ }
+ else
+ {
+ // ### fix me - this is silly.
+ char16_t *unicodeString =
+ nsTextFormatter::smprintf(unicodeFormatter, value.get());
+ filter->SetFilterName(nsDependentString(unicodeString));
+ nsTextFormatter::smprintf_free(unicodeString);
+ }
+ 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::Label)
+ {
+ // upgrade label to corresponding tag/keyword
+ nsresult res;
+ int32_t labelInt = value.ToInteger(&res);
+ if (NS_SUCCEEDED(res))
+ {
+ nsAutoCString keyword("$label");
+ keyword.Append('0' + labelInt);
+ currentFilterAction->SetType(nsMsgFilterAction::AddTag);
+ currentFilterAction->SetStrValue(keyword);
+ }
+ }
+ 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;
+ err = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ value, unicodeStr);
+ if (NS_FAILED(err))
+ break;
+
+ char *utf8 = ToNewUTF8String(unicodeStr);
+ value.Assign(utf8);
+ free(utf8);
+ }
+ 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"))
+ {
+ nsMsgSearchTerm *newTerm = new nsMsgSearchTerm;
+
+ if (newTerm)
+ {
+ newTerm->m_matchAll = true;
+ aFilter->AppendTerm(newTerm);
+ }
+ return (newTerm) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ 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)
+ {
+ nsMsgSearchTerm *newTerm = new nsMsgSearchTerm;
+
+ if (newTerm)
+ {
+ /* 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()
+{
+}
+
+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))
+ {
+ *aResult = filter;
+ break;
+ }
+ }
+
+ NS_IF_ADDREF(*aResult);
+ 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;
+
+ nsCOMPtr <nsISupportsArray> searchTerms;
+ uint32_t numSearchTerms=0;
+ filter->GetSearchTerms(getter_AddRefs(searchTerms));
+ if (searchTerms)
+ searchTerms->Count(&numSearchTerms);
+ for (uint32_t i = 0; i < numSearchTerms; i++)
+ {
+ filter->GetTerm(i, &attrib, nullptr, nullptr, nullptr, arbitraryHeader);
+ if (!arbitraryHeader.IsEmpty())
+ {
+ if (m_arbitraryHeaders.IsEmpty())
+ m_arbitraryHeaders.Assign(arbitraryHeader);
+ else if (m_arbitraryHeaders.Find(arbitraryHeader, CaseInsensitiveCompare) == -1)
+ {
+ 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
+ bool loggingEnabled = false;
+ nsresult rv = GetLoggingEnabled(&loggingEnabled);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (loggingEnabled)
+ {
+ nsCOMPtr <nsIOutputStream> logStream;
+ rv = GetLogStream(getter_AddRefs(logStream));
+ if (NS_SUCCEEDED(rv) && logStream) {
+ rv = logStream->Flush();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return rv;
+}
+// ------------ End FilterList methods ------------------