diff options
Diffstat (limited to 'mailnews/base/search/src/nsMsgFilterList.cpp')
-rw-r--r-- | mailnews/base/search/src/nsMsgFilterList.cpp | 1198 |
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 ------------------ |