From 302bf1b523012e11b60425d6eee1221ebc2724eb Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Sun, 3 Nov 2019 00:17:46 -0400 Subject: Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1 --- mailnews/base/search/src/Bogofilter.sfd | 14 + mailnews/base/search/src/DSPAM.sfd | 14 + mailnews/base/search/src/Habeas.sfd | 8 + mailnews/base/search/src/POPFile.sfd | 14 + mailnews/base/search/src/SpamAssassin.sfd | 14 + mailnews/base/search/src/SpamCatcher.sfd | 14 + mailnews/base/search/src/SpamPal.sfd | 14 + mailnews/base/search/src/moz.build | 33 + mailnews/base/search/src/nsMsgBodyHandler.cpp | 487 +++++ mailnews/base/search/src/nsMsgFilter.cpp | 1057 ++++++++++ mailnews/base/search/src/nsMsgFilter.h | 103 + mailnews/base/search/src/nsMsgFilterList.cpp | 1198 +++++++++++ mailnews/base/search/src/nsMsgFilterList.h | 75 + mailnews/base/search/src/nsMsgFilterService.cpp | 1216 ++++++++++++ mailnews/base/search/src/nsMsgFilterService.h | 46 + mailnews/base/search/src/nsMsgImapSearch.cpp | 1004 ++++++++++ mailnews/base/search/src/nsMsgLocalSearch.cpp | 1022 ++++++++++ mailnews/base/search/src/nsMsgLocalSearch.h | 104 + mailnews/base/search/src/nsMsgSearchAdapter.cpp | 1332 +++++++++++++ mailnews/base/search/src/nsMsgSearchImap.h | 37 + mailnews/base/search/src/nsMsgSearchNews.cpp | 511 +++++ mailnews/base/search/src/nsMsgSearchNews.h | 49 + mailnews/base/search/src/nsMsgSearchSession.cpp | 675 +++++++ mailnews/base/search/src/nsMsgSearchSession.h | 98 + mailnews/base/search/src/nsMsgSearchTerm.cpp | 2088 ++++++++++++++++++++ mailnews/base/search/src/nsMsgSearchValue.cpp | 117 ++ mailnews/base/search/src/nsMsgSearchValue.h | 26 + mailnews/base/search/src/nsMsgTraitService.js | 239 +++ .../base/search/src/nsMsgTraitService.manifest | 2 + 29 files changed, 11611 insertions(+) create mode 100644 mailnews/base/search/src/Bogofilter.sfd create mode 100644 mailnews/base/search/src/DSPAM.sfd create mode 100644 mailnews/base/search/src/Habeas.sfd create mode 100644 mailnews/base/search/src/POPFile.sfd create mode 100644 mailnews/base/search/src/SpamAssassin.sfd create mode 100644 mailnews/base/search/src/SpamCatcher.sfd create mode 100644 mailnews/base/search/src/SpamPal.sfd create mode 100644 mailnews/base/search/src/moz.build create mode 100644 mailnews/base/search/src/nsMsgBodyHandler.cpp create mode 100644 mailnews/base/search/src/nsMsgFilter.cpp create mode 100644 mailnews/base/search/src/nsMsgFilter.h create mode 100644 mailnews/base/search/src/nsMsgFilterList.cpp create mode 100644 mailnews/base/search/src/nsMsgFilterList.h create mode 100644 mailnews/base/search/src/nsMsgFilterService.cpp create mode 100644 mailnews/base/search/src/nsMsgFilterService.h create mode 100644 mailnews/base/search/src/nsMsgImapSearch.cpp create mode 100644 mailnews/base/search/src/nsMsgLocalSearch.cpp create mode 100644 mailnews/base/search/src/nsMsgLocalSearch.h create mode 100644 mailnews/base/search/src/nsMsgSearchAdapter.cpp create mode 100644 mailnews/base/search/src/nsMsgSearchImap.h create mode 100644 mailnews/base/search/src/nsMsgSearchNews.cpp create mode 100644 mailnews/base/search/src/nsMsgSearchNews.h create mode 100644 mailnews/base/search/src/nsMsgSearchSession.cpp create mode 100644 mailnews/base/search/src/nsMsgSearchSession.h create mode 100644 mailnews/base/search/src/nsMsgSearchTerm.cpp create mode 100644 mailnews/base/search/src/nsMsgSearchValue.cpp create mode 100644 mailnews/base/search/src/nsMsgSearchValue.h create mode 100644 mailnews/base/search/src/nsMsgTraitService.js create mode 100644 mailnews/base/search/src/nsMsgTraitService.manifest (limited to 'mailnews/base/search/src') diff --git a/mailnews/base/search/src/Bogofilter.sfd b/mailnews/base/search/src/Bogofilter.sfd new file mode 100644 index 000000000..a29a818e6 --- /dev/null +++ b/mailnews/base/search/src/Bogofilter.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="BogofilterYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-Bogosity\",begins with,Spam) OR (\"X-Bogosity\",begins with,Y)" +name="BogofilterNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-Bogosity\",begins with,Ham) OR (\"X-Bogosity\",begins with,N)" diff --git a/mailnews/base/search/src/DSPAM.sfd b/mailnews/base/search/src/DSPAM.sfd new file mode 100644 index 000000000..40bc00df7 --- /dev/null +++ b/mailnews/base/search/src/DSPAM.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="DSPAMYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-DSPAM-Result\",begins with,Spam)" +name="DSPAMNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-DSPAM-Result\",begins with,Innocent)" diff --git a/mailnews/base/search/src/Habeas.sfd b/mailnews/base/search/src/Habeas.sfd new file mode 100644 index 000000000..ceffff16e --- /dev/null +++ b/mailnews/base/search/src/Habeas.sfd @@ -0,0 +1,8 @@ +version="8" +logging="yes" +name="HabeasNo" +enabled="yes" +type="1" +action="JunkScore" +actionValue="0" +condition="OR (\"X-Habeas-SWE-3\",is,\"like Habeas SWE (tm)\")" diff --git a/mailnews/base/search/src/POPFile.sfd b/mailnews/base/search/src/POPFile.sfd new file mode 100644 index 000000000..a791705aa --- /dev/null +++ b/mailnews/base/search/src/POPFile.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="POPFileYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-Text-Classification\",begins with,spam)" +name="POPFileNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-Text-Classification\",begins with,inbox) OR (\"X-Text-Classification\",begins with,allowed)" diff --git a/mailnews/base/search/src/SpamAssassin.sfd b/mailnews/base/search/src/SpamAssassin.sfd new file mode 100644 index 000000000..d8d0ecdb1 --- /dev/null +++ b/mailnews/base/search/src/SpamAssassin.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="SpamAssassinYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-Spam-Status\",begins with,Yes) OR (\"X-Spam-Flag\",begins with,YES) OR (subject,begins with,***SPAM***)" +name="SpamAssassinNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-Spam-Status\",begins with,No)" diff --git a/mailnews/base/search/src/SpamCatcher.sfd b/mailnews/base/search/src/SpamCatcher.sfd new file mode 100644 index 000000000..d16cd80a2 --- /dev/null +++ b/mailnews/base/search/src/SpamCatcher.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="SpamCatcherNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"x-SpamCatcher\",begins with,No)" +name="SpamCatcherYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"x-SpamCatcher\",begins with,Yes)" diff --git a/mailnews/base/search/src/SpamPal.sfd b/mailnews/base/search/src/SpamPal.sfd new file mode 100644 index 000000000..830b1937b --- /dev/null +++ b/mailnews/base/search/src/SpamPal.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="SpamPalNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-SpamPal\",begins with,PASS)" +name="SpamPalYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-SpamPal\",begins with,SPAM)" diff --git a/mailnews/base/search/src/moz.build b/mailnews/base/search/src/moz.build new file mode 100644 index 000000000..ae3a57fa5 --- /dev/null +++ b/mailnews/base/search/src/moz.build @@ -0,0 +1,33 @@ +# vim: set filetype=python: +# 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/. + +SOURCES += [ + 'nsMsgBodyHandler.cpp', + 'nsMsgFilter.cpp', + 'nsMsgFilterList.cpp', + 'nsMsgFilterService.cpp', + 'nsMsgImapSearch.cpp', + 'nsMsgLocalSearch.cpp', + 'nsMsgSearchAdapter.cpp', + 'nsMsgSearchNews.cpp', + 'nsMsgSearchSession.cpp', + 'nsMsgSearchTerm.cpp', + 'nsMsgSearchValue.cpp', +] + +EXTRA_COMPONENTS += [ + 'nsMsgTraitService.js', + 'nsMsgTraitService.manifest', +] + +FINAL_LIBRARY = 'mail' + +FINAL_TARGET_FILES.isp += [ + 'Bogofilter.sfd', + 'DSPAM.sfd', + 'POPFile.sfd', + 'SpamAssassin.sfd', + 'SpamPal.sfd', +] diff --git a/mailnews/base/search/src/nsMsgBodyHandler.cpp b/mailnews/base/search/src/nsMsgBodyHandler.cpp new file mode 100644 index 000000000..873713bbb --- /dev/null +++ b/mailnews/base/search/src/nsMsgBodyHandler.cpp @@ -0,0 +1,487 @@ +/* -*- 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/. */ + +#include "msgCore.h" +#include "nsMsgSearchCore.h" +#include "nsMsgUtils.h" +#include "nsMsgBodyHandler.h" +#include "nsMsgSearchTerm.h" +#include "nsIMsgHdr.h" +#include "nsMsgMessageFlags.h" +#include "nsISeekableStream.h" +#include "nsIInputStream.h" +#include "nsIFile.h" +#include "plbase64.h" +#include "prmem.h" +#include "nsMimeTypes.h" + +nsMsgBodyHandler::nsMsgBodyHandler (nsIMsgSearchScopeTerm * scope, + uint32_t numLines, + nsIMsgDBHdr* msg, nsIMsgDatabase * db) +{ + m_scope = scope; + m_numLocalLines = numLines; + uint32_t flags; + m_lineCountInBodyLines = NS_SUCCEEDED(msg->GetFlags(&flags)) ? + !(flags & nsMsgMessageFlags::Offline) : true; + // account for added x-mozilla-status lines, and envelope line. + if (!m_lineCountInBodyLines) + m_numLocalLines += 3; + m_msgHdr = msg; + m_db = db; + + // the following are variables used when the body handler is handling stuff from filters....through this constructor, that is not the + // case so we set them to NULL. + m_headers = NULL; + m_headersSize = 0; + m_Filtering = false; // make sure we set this before we call initialize... + + Initialize(); // common initialization stuff + OpenLocalFolder(); +} + +nsMsgBodyHandler::nsMsgBodyHandler(nsIMsgSearchScopeTerm * scope, + uint32_t numLines, + nsIMsgDBHdr* msg, nsIMsgDatabase* db, + const char * headers, uint32_t headersSize, + bool Filtering) +{ + m_scope = scope; + m_numLocalLines = numLines; + uint32_t flags; + m_lineCountInBodyLines = NS_SUCCEEDED(msg->GetFlags(&flags)) ? + !(flags & nsMsgMessageFlags::Offline) : true; + // account for added x-mozilla-status lines, and envelope line. + if (!m_lineCountInBodyLines) + m_numLocalLines += 3; + m_msgHdr = msg; + m_db = db; + m_headersSize = headersSize; + m_Filtering = Filtering; + + Initialize(); + + if (m_Filtering) + m_headers = headers; + else + OpenLocalFolder(); // if nothing else applies, then we must be a POP folder file +} + +void nsMsgBodyHandler::Initialize() +// common initialization code regardless of what body type we are handling... +{ + // Default transformations for local message search and MAPI access + m_stripHeaders = true; + m_stripHtml = true; + m_partIsHtml = false; + m_base64part = false; + m_isMultipart = false; + m_partIsText = true; // Default is text/plain, maybe proven otherwise later. + m_pastMsgHeaders = false; + m_pastPartHeaders = false; + m_inMessageAttachment = false; + m_headerBytesRead = 0; +} + +nsMsgBodyHandler::~nsMsgBodyHandler() +{ +} + +int32_t nsMsgBodyHandler::GetNextLine (nsCString &buf, nsCString &charset) +{ + int32_t length = -1; // length of incoming line or -1 eof + int32_t outLength = -1; // length of outgoing line or -1 eof + bool eatThisLine = true; + nsAutoCString nextLine; + + while (eatThisLine) { + // first, handle the filtering case...this is easy.... + if (m_Filtering) + length = GetNextFilterLine(nextLine); + else + { + // 3 cases: Offline IMAP, POP, or we are dealing with a news message.... + // Offline cases should be same as local mail cases, since we're going + // to store offline messages in berkeley format folders. + if (m_db) + { + length = GetNextLocalLine (nextLine); // (2) POP + } + } + + if (length < 0) + break; // eof in + + outLength = ApplyTransformations(nextLine, length, eatThisLine, buf); + } + + if (outLength < 0) + return -1; // eof out + + // For non-multipart messages, the entire message minus headers is encoded + // ApplyTransformations can only decode a part + if (!m_isMultipart && m_base64part) + { + Base64Decode(buf); + m_base64part = false; + // And reapply our transformations... + outLength = ApplyTransformations(buf, buf.Length(), eatThisLine, buf); + } + + charset = m_partCharset; + return outLength; +} + +void nsMsgBodyHandler::OpenLocalFolder() +{ + nsCOMPtr inputStream; + nsresult rv = m_scope->GetInputStream(m_msgHdr, getter_AddRefs(inputStream)); + // Warn and return if GetInputStream fails + NS_ENSURE_SUCCESS_VOID(rv); + m_fileLineStream = do_QueryInterface(inputStream); +} + +int32_t nsMsgBodyHandler::GetNextFilterLine(nsCString &buf) +{ + // m_nextHdr always points to the next header in the list....the list is NULL terminated... + uint32_t numBytesCopied = 0; + if (m_headersSize > 0) + { + // #mscott. Ugly hack! filter headers list have CRs & LFs inside the NULL delimited list of header + // strings. It is possible to have: To NULL CR LF From. We want to skip over these CR/LFs if they start + // at the beginning of what we think is another header. + + while (m_headersSize > 0 && (m_headers[0] == '\r' || m_headers[0] == '\n' || m_headers[0] == ' ' || m_headers[0] == '\0')) + { + m_headers++; // skip over these chars... + m_headersSize--; + } + + if (m_headersSize > 0) + { + numBytesCopied = strlen(m_headers) + 1 ; + buf.Assign(m_headers); + m_headers += numBytesCopied; + // be careful...m_headersSize is unsigned. Don't let it go negative or we overflow to 2^32....*yikes* + if (m_headersSize < numBytesCopied) + m_headersSize = 0; + else + m_headersSize -= numBytesCopied; // update # bytes we have read from the headers list + + return (int32_t) numBytesCopied; + } + } + else if (m_headersSize == 0) { + buf.Truncate(); + } + return -1; +} + +// return -1 if no more local lines, length of next line otherwise. + +int32_t nsMsgBodyHandler::GetNextLocalLine(nsCString &buf) +// returns number of bytes copied +{ + if (m_numLocalLines) + { + // I the line count is in body lines, only decrement once we have + // processed all the headers. Otherwise the line is not in body + // lines and we want to decrement for every line. + if (m_pastMsgHeaders || !m_lineCountInBodyLines) + m_numLocalLines--; + // do we need to check the return value here? + if (m_fileLineStream) + { + bool more = false; + nsresult rv = m_fileLineStream->ReadLine(buf, &more); + if (NS_SUCCEEDED(rv)) + return buf.Length(); + } + } + + return -1; +} + +/** + * This method applies a sequence of transformations to the line. + * + * It applies the following sequences in order + * * Removes headers if the searcher doesn't want them + * (sets m_past*Headers) + * * Determines the current MIME type. + * (via SniffPossibleMIMEHeader) + * * Strips any HTML if the searcher doesn't want it + * * Strips non-text parts + * * Decodes any base64 part + * (resetting part variables: m_base64part, m_pastPartHeaders, m_partIsHtml, + * m_partIsText) + * + * @param line (in) the current line + * @param length (in) the length of said line + * @param eatThisLine (out) whether or not to ignore this line + * @param buf (inout) if m_base64part, the current part as needed for + * decoding; else, it is treated as an out param (a + * redundant version of line). + * @return the length of the line after applying transformations + */ +int32_t nsMsgBodyHandler::ApplyTransformations (const nsCString &line, int32_t length, + bool &eatThisLine, nsCString &buf) +{ + eatThisLine = false; + + if (!m_pastPartHeaders) // line is a line from the part headers + { + if (m_stripHeaders) + eatThisLine = true; + + // We have already grabbed all worthwhile information from the headers, + // so there is no need to keep track of the current lines + buf.Assign(line); + + SniffPossibleMIMEHeader(buf); + + if (buf.IsEmpty() || buf.First() == '\r' || buf.First() == '\n') { + if (!m_inMessageAttachment) { + m_pastPartHeaders = true; + } else { + // We're in a message attachment and have just read past the + // part header for the attached message. We now need to read + // the message headers and any part headers. + // We can now forget about the special handling of attached messages. + m_inMessageAttachment = false; + } + } + + // We set m_pastMsgHeaders to 'true' only once. + if (m_pastPartHeaders) + m_pastMsgHeaders = true; + + return length; + } + + // Check to see if this is one of our boundary strings. + bool matchedBoundary = false; + if (m_isMultipart && m_boundaries.Length() > 0) { + for (int32_t i = (int32_t)m_boundaries.Length() - 1; i >= 0; i--) { + if (StringBeginsWith(line, m_boundaries[i])) { + matchedBoundary = true; + // If we matched a boundary, we won't need the nested/later ones any more. + m_boundaries.SetLength(i+1); + break; + } + } + } + if (matchedBoundary) + { + if (m_base64part && m_partIsText) + { + Base64Decode(buf); + // Work on the parsed string + if (!buf.Length()) + { + NS_WARNING("Trying to transform an empty buffer"); + eatThisLine = true; + } + else + { + // It is wrong to call ApplyTransformations() here since this will + // lead to the buffer being doubled-up at |buf.Append(line.get());| below. + // ApplyTransformations(buf, buf.Length(), eatThisLine, buf); + // Avoid spurious failures + eatThisLine = false; + } + } + else + { + buf.Truncate(); + eatThisLine = true; // We have no content... + } + + // Reset all assumed headers + m_base64part = false; + // Get ready to sniff new part headers, but do not reset m_pastMsgHeaders + // since it will screw the body line count. + m_pastPartHeaders = false; + m_partIsHtml = false; + // If we ever see a multipart message, each part needs to set 'm_partIsText', + // so no more defaulting to 'true' when the part is done. + m_partIsText = false; + + return buf.Length(); + } + + if (!m_partIsText) + { + // Ignore non-text parts + buf.Truncate(); + eatThisLine = true; + return 0; + } + + if (m_base64part) + { + // We need to keep track of all lines to parse base64encoded... + buf.Append(line.get()); + eatThisLine = true; + return buf.Length(); + } + + // ... but there's no point if we're not parsing base64. + buf.Assign(line); + if (m_stripHtml && m_partIsHtml) + { + StripHtml (buf); + } + + return buf.Length(); +} + +void nsMsgBodyHandler::StripHtml (nsCString &pBufInOut) +{ + char *pBuf = (char*) PR_Malloc (pBufInOut.Length() + 1); + if (pBuf) + { + char *pWalk = pBuf; + + char *pWalkInOut = (char *) pBufInOut.get(); + bool inTag = false; + while (*pWalkInOut) // throw away everything inside < > + { + if (!inTag) + if (*pWalkInOut == '<') + inTag = true; + else + *pWalk++ = *pWalkInOut; + else + if (*pWalkInOut == '>') + inTag = false; + pWalkInOut++; + } + *pWalk = 0; // null terminator + + pBufInOut.Adopt(pBuf); + } +} + +/** + * Determines the MIME type, if present, from the current line. + * + * m_partIsHtml, m_isMultipart, m_partIsText, m_base64part, and boundary are + * all set by this method at various points in time. + * + * @param line (in) a header line that may contain a MIME header + */ +void nsMsgBodyHandler::SniffPossibleMIMEHeader(const nsCString &line) +{ + // Some parts of MIME are case-sensitive and other parts are case-insensitive; + // specifically, the headers are all case-insensitive and the values we care + // about are also case-insensitive, with the sole exception of the boundary + // string, so we can't just take the input line and make it lower case. + nsCString lowerCaseLine(line); + ToLowerCase(lowerCaseLine); + + if (StringBeginsWith(lowerCaseLine, NS_LITERAL_CSTRING("content-type:"))) + { + if (lowerCaseLine.Find("text/html", CaseInsensitiveCompare) != -1) + { + m_partIsText = true; + m_partIsHtml = true; + } + else if (lowerCaseLine.Find("multipart/", CaseInsensitiveCompare) != -1) + { + if (m_isMultipart) + { + // Nested multipart, get ready for new headers. + m_base64part = false; + m_pastPartHeaders = false; + m_partIsHtml = false; + m_partIsText = false; + } + m_isMultipart = true; + m_partCharset.Truncate(); + } + else if (lowerCaseLine.Find("message/", CaseInsensitiveCompare) != -1) + { + // Initialise again. + m_base64part = false; + m_pastPartHeaders = false; + m_partIsHtml = false; + m_partIsText = true; // Default is text/plain, maybe proven otherwise later. + m_inMessageAttachment = true; + } + else if (lowerCaseLine.Find("text/", CaseInsensitiveCompare) != -1) + m_partIsText = true; + else if (lowerCaseLine.Find("text/", CaseInsensitiveCompare) == -1) + m_partIsText = false; // We have disproven our assumption. + } + + int32_t start; + if (m_isMultipart && + (start = lowerCaseLine.Find("boundary=", CaseInsensitiveCompare)) != -1) + { + start += 9; // strlen("boundary=") + if (line[start] == '\"') + start++; + int32_t end = line.RFindChar('\"'); + if (end == -1) + end = line.Length(); + + // Collect all boundaries. Since we only react to crossing a boundary, + // we can simply collect the boundaries instead of forming a tree + // structure from the message. Keep it simple ;-) + nsCString boundary; + boundary.Assign("--"); + boundary.Append(Substring(line, start, end-start)); + if (!m_boundaries.Contains(boundary)) + m_boundaries.AppendElement(boundary); + } + + if (m_isMultipart && + (start = lowerCaseLine.Find("charset=", CaseInsensitiveCompare)) != -1) + { + start += 8; // strlen("charset=") + bool foundQuote = false; + if (line[start] == '\"') { + start++; + foundQuote = true; + } + int32_t end = line.FindChar(foundQuote ? '\"' : ';', start); + if (end == -1) + end = line.Length(); + + m_partCharset.Assign(Substring(line, start, end-start)); + } + + if (StringBeginsWith(lowerCaseLine, + NS_LITERAL_CSTRING("content-transfer-encoding:")) && + lowerCaseLine.Find(ENCODING_BASE64, CaseInsensitiveCompare) != kNotFound) + m_base64part = true; +} + +/** + * Decodes the given base64 string. + * + * It returns its decoded string in its input. + * + * @param pBufInOut (inout) a buffer of the string + */ +void nsMsgBodyHandler::Base64Decode (nsCString &pBufInOut) +{ + char *decodedBody = PL_Base64Decode(pBufInOut.get(), pBufInOut.Length(), nullptr); + if (decodedBody) + pBufInOut.Adopt(decodedBody); + + int32_t offset = pBufInOut.FindChar('\n'); + while (offset != -1) { + pBufInOut.Replace(offset, 1, ' '); + offset = pBufInOut.FindChar('\n', offset); + } + offset = pBufInOut.FindChar('\r'); + while (offset != -1) { + pBufInOut.Replace(offset, 1, ' '); + offset = pBufInOut.FindChar('\r', offset); + } +} + diff --git a/mailnews/base/search/src/nsMsgFilter.cpp b/mailnews/base/search/src/nsMsgFilter.cpp new file mode 100644 index 000000000..e94240f29 --- /dev/null +++ b/mailnews/base/search/src/nsMsgFilter.cpp @@ -0,0 +1,1057 @@ +/* -*- Mode: C++; 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 nsMsgFilter interface + +#include "msgCore.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgHdr.h" +#include "nsMsgFilterList.h" // for kFileVersion +#include "nsMsgFilter.h" +#include "nsMsgUtils.h" +#include "nsMsgLocalSearch.h" +#include "nsMsgSearchTerm.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgIncomingServer.h" +#include "nsMsgSearchValue.h" +#include "nsMsgI18N.h" +#include "nsIOutputStream.h" +#include "nsIStringBundle.h" +#include "nsDateTimeFormatCID.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIMsgFilterService.h" +#include "nsIMutableArray.h" +#include "prmem.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Services.h" + +static const char *kImapPrefix = "//imap:"; +static const char *kWhitespace = "\b\t\r\n "; + +nsMsgRuleAction::nsMsgRuleAction() +{ +} + +nsMsgRuleAction::~nsMsgRuleAction() +{ +} + +NS_IMPL_ISUPPORTS(nsMsgRuleAction, nsIMsgRuleAction) + +NS_IMPL_GETSET(nsMsgRuleAction, Type, nsMsgRuleActionType, m_type) + +NS_IMETHODIMP nsMsgRuleAction::SetPriority(nsMsgPriorityValue aPriority) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::ChangePriority, + NS_ERROR_ILLEGAL_VALUE); + m_priority = aPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetPriority(nsMsgPriorityValue *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::ChangePriority, + NS_ERROR_ILLEGAL_VALUE); + *aResult = m_priority; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetLabel(nsMsgLabelValue aLabel) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::Label, + NS_ERROR_ILLEGAL_VALUE); + m_label = aLabel; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetLabel(nsMsgLabelValue *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::Label, NS_ERROR_ILLEGAL_VALUE); + *aResult = m_label; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetTargetFolderUri(const nsACString &aUri) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::MoveToFolder || + m_type == nsMsgFilterAction::CopyToFolder, + NS_ERROR_ILLEGAL_VALUE); + m_folderUri = aUri; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetTargetFolderUri(nsACString &aResult) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::MoveToFolder || + m_type == nsMsgFilterAction::CopyToFolder, + NS_ERROR_ILLEGAL_VALUE); + aResult = m_folderUri; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetJunkScore(int32_t aJunkScore) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::JunkScore && aJunkScore >= 0 && aJunkScore <= 100, + NS_ERROR_ILLEGAL_VALUE); + m_junkScore = aJunkScore; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetJunkScore(int32_t *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::JunkScore, NS_ERROR_ILLEGAL_VALUE); + *aResult = m_junkScore; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetStrValue(const nsACString &aStrValue) +{ + m_strValue = aStrValue; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetStrValue(nsACString &aStrValue) +{ + aStrValue = m_strValue; + return NS_OK; +} + +/* attribute ACString customId; */ +NS_IMETHODIMP nsMsgRuleAction::GetCustomId(nsACString & aCustomId) +{ + aCustomId = m_customId; + return NS_OK; +} + +NS_IMETHODIMP nsMsgRuleAction::SetCustomId(const nsACString & aCustomId) +{ + m_customId = aCustomId; + return NS_OK; +} + +// this can only be called after the customId is set +NS_IMETHODIMP nsMsgRuleAction::GetCustomAction(nsIMsgFilterCustomAction **aCustomAction) +{ + NS_ENSURE_ARG_POINTER(aCustomAction); + if (!m_customAction) + { + if (m_customId.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + nsresult rv; + nsCOMPtr filterService = + do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = filterService->GetCustomAction(m_customId, getter_AddRefs(m_customAction)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // found the correct custom action + NS_ADDREF(*aCustomAction = m_customAction); + return NS_OK; +} + +nsMsgFilter::nsMsgFilter(): + m_temporary(false), + m_unparseable(false), + m_filterList(nullptr), + m_expressionTree(nullptr) +{ + nsresult rv = NS_NewISupportsArray(getter_AddRefs(m_termList)); + if (NS_FAILED(rv)) + NS_ASSERTION(false, "Failed to allocate a nsISupportsArray for nsMsgFilter"); + + m_type = nsMsgFilterType::InboxRule | nsMsgFilterType::Manual; +} + +nsMsgFilter::~nsMsgFilter() +{ + delete m_expressionTree; +} + +NS_IMPL_ISUPPORTS(nsMsgFilter, nsIMsgFilter) + +NS_IMPL_GETSET(nsMsgFilter, FilterType, nsMsgFilterTypeType, m_type) +NS_IMPL_GETSET(nsMsgFilter, Enabled, bool, m_enabled) +NS_IMPL_GETSET(nsMsgFilter, Temporary, bool, m_temporary) +NS_IMPL_GETSET(nsMsgFilter, Unparseable, bool, m_unparseable) + +NS_IMETHODIMP nsMsgFilter::GetFilterName(nsAString &name) +{ + name = m_filterName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetFilterName(const nsAString &name) +{ + m_filterName.Assign(name); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetFilterDesc(nsACString &description) +{ + description = m_description; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetFilterDesc(const nsACString &description) +{ + m_description.Assign(description); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetUnparsedBuffer(nsACString &unparsedBuffer) +{ + unparsedBuffer = m_unparsedBuffer; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetUnparsedBuffer(const nsACString &unparsedBuffer) +{ + m_unparsedBuffer.Assign(unparsedBuffer); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::AddTerm( + nsMsgSearchAttribValue attrib, /* attribute for this term */ + nsMsgSearchOpValue op, /* operator e.g. opContains */ + nsIMsgSearchValue *value, /* value e.g. "Dogbert" */ + bool BooleanAND, /* true if AND is the boolean operator. + false if OR is the boolean operators */ + const nsACString & arbitraryHeader) /* arbitrary header specified by user. + ignored unless attrib = attribOtherHeader */ +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::AppendTerm(nsIMsgSearchTerm * aTerm) +{ + NS_ENSURE_TRUE(aTerm, NS_ERROR_NULL_POINTER); + // invalidate expression tree if we're changing the terms + delete m_expressionTree; + m_expressionTree = nullptr; + return m_termList->AppendElement(static_cast(aTerm)); +} + +NS_IMETHODIMP +nsMsgFilter::CreateTerm(nsIMsgSearchTerm **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + nsMsgSearchTerm *term = new nsMsgSearchTerm; + NS_ENSURE_TRUE(term, NS_ERROR_OUT_OF_MEMORY); + + *aResult = static_cast(term); + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::CreateAction(nsIMsgRuleAction **aAction) +{ + NS_ENSURE_ARG_POINTER(aAction); + nsMsgRuleAction *action = new nsMsgRuleAction; + NS_ENSURE_TRUE(action, NS_ERROR_OUT_OF_MEMORY); + + *aAction = static_cast(action); + NS_ADDREF(*aAction); + return NS_OK; +} + +// All the rules' actions form a unit, with no real order imposed. +// But certain actions like MoveToFolder or StopExecution would make us drop +// consecutive actions, while actions like AddTag implicitly care about the +// order of invocation. Hence we do as little reordering as possible, keeping +// the user-defined order as much as possible. +// We explicitly don't allow for filters which do "tag message as Important, +// copy it to another folder, tag it as To Do also, copy this different state +// elsewhere" in one go. You need to define separate filters for that. +// +// The order of actions returned by this method: +// index action(s) +// ------- --------- +// 0 FetchBodyFromPop3Server +// 1..n all other 'normal' actions, in their original order +// n+1..m CopyToFolder +// m+1 MoveToFolder or Delete +// m+2 StopExecution +NS_IMETHODIMP +nsMsgFilter::GetSortedActionList(nsIArray **aActionList) +{ + NS_ENSURE_ARG_POINTER(aActionList); + + uint32_t numActions; + nsresult rv = GetActionCount(&numActions); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr orderedActions(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // hold separate pointers into the action list + uint32_t nextIndexForNormal = 0, nextIndexForCopy = 0, nextIndexForMove = 0; + for (uint32_t index = 0; index < numActions; ++index) + { + nsCOMPtr action; + rv = GetActionAt(index, getter_AddRefs(action)); + if (NS_FAILED(rv) || !action) + continue; + + nsMsgRuleActionType actionType; + action->GetType(&actionType); + switch (actionType) + { + case nsMsgFilterAction::FetchBodyFromPop3Server: + { + // always insert in front + rv = orderedActions->InsertElementAt(action, 0, false); + NS_ENSURE_SUCCESS(rv, rv); + ++nextIndexForNormal; + ++nextIndexForCopy; + ++nextIndexForMove; + break; + } + + case nsMsgFilterAction::CopyToFolder: + { + // insert into copy actions block, in order of appearance + rv = orderedActions->InsertElementAt(action, nextIndexForCopy, false); + NS_ENSURE_SUCCESS(rv, rv); + ++nextIndexForCopy; + ++nextIndexForMove; + break; + } + + case nsMsgFilterAction::MoveToFolder: + case nsMsgFilterAction::Delete: + { + // insert into move/delete action block + rv = orderedActions->InsertElementAt(action, nextIndexForMove, false); + NS_ENSURE_SUCCESS(rv, rv); + ++nextIndexForMove; + break; + } + + case nsMsgFilterAction::StopExecution: + { + // insert into stop action block + rv = orderedActions->AppendElement(action, false); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + + default: + { + // insert into normal action block, in order of appearance + rv = orderedActions->InsertElementAt(action, nextIndexForNormal, false); + NS_ENSURE_SUCCESS(rv, rv); + ++nextIndexForNormal; + ++nextIndexForCopy; + ++nextIndexForMove; + break; + } + } + } + + orderedActions.forget(aActionList); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::AppendAction(nsIMsgRuleAction *aAction) +{ + NS_ENSURE_ARG_POINTER(aAction); + + m_actionList.AppendElement(aAction); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetActionAt(uint32_t aIndex, nsIMsgRuleAction **aAction) +{ + NS_ENSURE_ARG_POINTER(aAction); + NS_ENSURE_ARG(aIndex < m_actionList.Length()); + + NS_ENSURE_TRUE(*aAction = m_actionList[aIndex], NS_ERROR_ILLEGAL_VALUE); + NS_IF_ADDREF(*aAction); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetActionIndex(nsIMsgRuleAction *aAction, int32_t *aIndex) +{ + NS_ENSURE_ARG_POINTER(aIndex); + + *aIndex = m_actionList.IndexOf(aAction); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetActionCount(uint32_t *aCount) +{ + NS_ENSURE_ARG_POINTER(aCount); + + *aCount = m_actionList.Length(); + return NS_OK; +} + +NS_IMETHODIMP //for editing a filter +nsMsgFilter::ClearActionList() +{ + m_actionList.Clear(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetTerm(int32_t termIndex, + nsMsgSearchAttribValue *attrib, /* attribute for this term */ + nsMsgSearchOpValue *op, /* operator e.g. opContains */ + nsIMsgSearchValue **value, /* value e.g. "Dogbert" */ + bool *booleanAnd, /* true if AND is the boolean operator. false if OR is the boolean operator */ + nsACString &arbitraryHeader) /* arbitrary header specified by user.ignore unless attrib = attribOtherHeader */ +{ + nsCOMPtr term; + nsresult rv = m_termList->QueryElementAt(termIndex, NS_GET_IID(nsIMsgSearchTerm), + (void **)getter_AddRefs(term)); + if (NS_SUCCEEDED(rv) && term) + { + if (attrib) + term->GetAttrib(attrib); + if (op) + term->GetOp(op); + if (value) + term->GetValue(value); + if (booleanAnd) + term->GetBooleanAnd(booleanAnd); + if (attrib && *attrib > nsMsgSearchAttrib::OtherHeader + && *attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) + term->GetArbitraryHeader(arbitraryHeader); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetSearchTerms(nsISupportsArray **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + // caller can change m_termList, which can invalidate m_expressionTree. + delete m_expressionTree; + m_expressionTree = nullptr; + NS_IF_ADDREF(*aResult = m_termList); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetSearchTerms(nsISupportsArray *aSearchList) +{ + delete m_expressionTree; + m_expressionTree = nullptr; + m_termList = aSearchList; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetScope(nsIMsgSearchScopeTerm *aResult) +{ + m_scope = aResult; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetScope(nsIMsgSearchScopeTerm **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_IF_ADDREF(*aResult = m_scope); + return NS_OK; +} + +#define LOG_ENTRY_START_TAG "

\n" +#define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG)) +#define LOG_ENTRY_END_TAG "

\n" +#define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG)) +// Does this need to be localizable? +#define LOG_ENTRY_TIMESTAMP "[$S] " + +// This function handles the logging both for success of filtering +// (NS_SUCCEEDED(aRcode)), and for error reporting (NS_FAILED(aRcode) +// when the filter action (such as file move/copy) failed. +// +// @param aRcode NS_OK for successful filtering +// operation, otherwise, an error code for filtering failure. +// @param aErrmsg Not used for success case (ignored), and a non-null +// error message for failure case. +// +// CAUTION: Unless logging is enabled, no error/warning is shown. +// So enable logging if you would like to see the error/warning. +// +// XXX The current code in this file does not report errors of minor +// operations such as adding labels and so forth which may fail when +// underlying file system for the message store experiences +// failure. For now, most visible major errors such as message +// move/copy failures are taken care of. +// +// XXX Possible Improvement: For error case reporting, someone might +// want to implement a transient message that appears and stick until +// the user clears in the message status bar, etc. For now, we log an +// error in a similar form as a conventional successful filter event +// with additional error information at the beginning. +// +nsresult +nsMsgFilter::LogRuleHitGeneric(nsIMsgRuleAction *aFilterAction, + nsIMsgDBHdr *aMsgHdr, + nsresult aRcode, + const char *aErrmsg) +{ + NS_ENSURE_ARG_POINTER(aFilterAction); + NS_ENSURE_ARG_POINTER(aMsgHdr); + + NS_ENSURE_TRUE(m_filterList, NS_OK); + nsCOMPtr logStream; + nsresult rv = m_filterList->GetLogStream(getter_AddRefs(logStream)); + NS_ENSURE_SUCCESS(rv,rv); + + PRTime date; + nsMsgRuleActionType actionType; + + nsString authorValue; + nsString subjectValue; + nsString filterName; + nsString dateValue; + + GetFilterName(filterName); + aFilterAction->GetType(&actionType); + (void)aMsgHdr->GetDate(&date); + PRExplodedTime exploded; + PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded); + + if (!mDateFormatter) + { + mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + if (!mDateFormatter) + { + return NS_ERROR_FAILURE; + } + } + mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort, + kTimeFormatSeconds, &exploded, + dateValue); + + (void)aMsgHdr->GetMime2DecodedAuthor(authorValue); + (void)aMsgHdr->GetMime2DecodedSubject(subjectValue); + + nsCString buffer; +#ifdef MOZILLA_INTERNAL_API + // this is big enough to hold a log entry. + // do this so we avoid growing and copying as we append to the log. + buffer.SetCapacity(512); +#endif + + nsCOMPtr bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/filter.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + // If error, prefix with the error code and error message. + // A desired wording (without NEWLINEs): + // Filter Action Failed "Move failed" with error code=0x80004005 + // while attempting: Applied filter "test" to message from + // Some Test - send test 3 at 2/13/2015 11:32:53 AM + // moved message id = 54DE5165.7000907@example.com to + // mailbox://nobody@Local%20Folders/test + + if (NS_FAILED(aRcode)) + { + + // Let us put "Filter Action Failed: "%s" with error code=%s while attempting: " inside bundle. + // Convert aErrmsg to UTF16 string, and + // convert aRcode to UTF16 string in advance. + + char tcode[20]; + PR_snprintf(tcode, sizeof(tcode), "0x%08x", aRcode); + + NS_ConvertASCIItoUTF16 tcode16(tcode) ; + NS_ConvertASCIItoUTF16 tErrmsg16(aErrmsg) ; + + const char16_t *logErrorFormatStrings[2] = { tErrmsg16.get(), tcode16.get()}; + nsString filterFailureWarningPrefix; + rv = bundle->FormatStringFromName( + u"filterFailureWarningPrefix", + logErrorFormatStrings, 2, + getter_Copies(filterFailureWarningPrefix)); + NS_ENSURE_SUCCESS(rv, rv); + buffer += NS_ConvertUTF16toUTF8(filterFailureWarningPrefix); + buffer += "\n"; + } + + const char16_t *filterLogDetectFormatStrings[4] = { filterName.get(), authorValue.get(), subjectValue.get(), dateValue.get() }; + nsString filterLogDetectStr; + rv = bundle->FormatStringFromName( + u"filterLogDetectStr", + filterLogDetectFormatStrings, 4, + getter_Copies(filterLogDetectStr)); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += NS_ConvertUTF16toUTF8(filterLogDetectStr); + buffer += "\n"; + + if (actionType == nsMsgFilterAction::MoveToFolder || + actionType == nsMsgFilterAction::CopyToFolder) + { + nsCString actionFolderUri; + aFilterAction->GetTargetFolderUri(actionFolderUri); + NS_ConvertASCIItoUTF16 actionFolderUriValue(actionFolderUri); + + nsCString msgId; + aMsgHdr->GetMessageId(getter_Copies(msgId)); + NS_ConvertASCIItoUTF16 msgIdValue(msgId); + + const char16_t *logMoveFormatStrings[2] = { msgIdValue.get(), actionFolderUriValue.get() }; + nsString logMoveStr; + rv = bundle->FormatStringFromName( + (actionType == nsMsgFilterAction::MoveToFolder) ? + u"logMoveStr" : u"logCopyStr", + logMoveFormatStrings, 2, + getter_Copies(logMoveStr)); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += NS_ConvertUTF16toUTF8(logMoveStr); + } + else if (actionType == nsMsgFilterAction::Custom) + { + nsCOMPtr customAction; + nsAutoString filterActionName; + rv = aFilterAction->GetCustomAction(getter_AddRefs(customAction)); + if (NS_SUCCEEDED(rv) && customAction) + customAction->GetName(filterActionName); + if (filterActionName.IsEmpty()) + bundle->GetStringFromName( + u"filterMissingCustomAction", + getter_Copies(filterActionName)); + buffer += NS_ConvertUTF16toUTF8(filterActionName); + } + else + { + nsString actionValue; + nsAutoString filterActionID; + filterActionID = NS_LITERAL_STRING("filterAction"); + filterActionID.AppendInt(actionType); + rv = bundle->GetStringFromName(filterActionID.get(), getter_Copies(actionValue)); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += NS_ConvertUTF16toUTF8(actionValue); + } + buffer += "\n"; + + // Prepare timestamp + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded); + mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort, + kTimeFormatSeconds, &exploded, + dateValue); + + nsCString timestampString(LOG_ENTRY_TIMESTAMP); + MsgReplaceSubstring(timestampString, "$S", NS_ConvertUTF16toUTF8(dateValue).get()); + + // XXX: Finally, here we have enough context and buffer + // (string) to display the filtering error if we want: for + // example, a sticky error message in status bar, etc. + + 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"); + + rv = logStream->Write(timestampString.get(), timestampString.Length(), &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == timestampString.Length(), "failed to write out timestamp"); + + // HTML-escape the log for security reasons. + // We don't want someone to send us a message with a subject with + // HTML tags, especially