/* -*- 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 // 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 "\n\n\n\n\n\n\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 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 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 folder; nsresult rv = GetFolder(getter_AddRefs(folder)); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr 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 thisFolder; rv = m_folder->GetFilePath(getter_AddRefs(thisFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 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 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 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 bufStream; err = NS_NewBufferedInputStream(getter_AddRefs(bufStream), aStream, FILE_IO_BUFFER_SIZE); NS_ENSURE_SUCCESS(err, err); nsMsgFilterFileAttribValue attrib; nsCOMPtr 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(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 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 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 tempFilter1; rv = GetFilterAt(newIndex, getter_AddRefs(tempFilter1)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 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 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 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 logStream; rv = GetLogStream(getter_AddRefs(logStream)); if (NS_SUCCEEDED(rv) && logStream) { rv = logStream->Flush(); NS_ENSURE_SUCCESS(rv,rv); } } return rv; } // ------------ End FilterList methods ------------------