diff options
Diffstat (limited to 'mailnews/base/search/src')
29 files changed, 11611 insertions, 0 deletions
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 <nsIInputStream> 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<nsIMsgFilterService> 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<nsISupports*>(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<nsIMsgSearchTerm*>(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<nsIMsgRuleAction*>(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<nsIMutableArray> 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<nsIMsgRuleAction> 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<nsIMsgSearchTerm> 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 "<p>\n" +#define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG)) +#define LOG_ENTRY_END_TAG "</p>\n" +#define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG)) +// 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 <nsIOutputStream> 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<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> 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 <test@example.com> - 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<nsIMsgFilterCustomAction> 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 <script>. + char *escapedBuffer = MsgEscapeHTML(buffer.get()); + if (!escapedBuffer) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t escapedBufferLen = strlen(escapedBuffer); + rv = logStream->Write(escapedBuffer, escapedBufferLen, &writeCount); + PR_Free(escapedBuffer); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == escapedBufferLen, "failed to write out log hit"); + + rv = logStream->Write(LOG_ENTRY_END_TAG, LOG_ENTRY_END_TAG_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == LOG_ENTRY_END_TAG_LEN, "failed to write out end log tag"); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::LogRuleHit(nsIMsgRuleAction *aFilterAction, + nsIMsgDBHdr *aMsgHdr) +{ + return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, NS_OK, nullptr); +} + +NS_IMETHODIMP nsMsgFilter::LogRuleHitFail(nsIMsgRuleAction *aFilterAction, + nsIMsgDBHdr *aMsgHdr, + nsresult aRcode, + const char *aErrMsg) +{ + return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, aRcode, aErrMsg); +} + +NS_IMETHODIMP +nsMsgFilter::MatchHdr(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder, + nsIMsgDatabase *db, const char *headers, + uint32_t headersSize, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(folder); + NS_ENSURE_ARG_POINTER(msgHdr); + // use offlineMail because + nsCString folderCharset; + folder->GetCharset(folderCharset); + nsresult rv = nsMsgSearchOfflineMail::MatchTermsForFilter(msgHdr, m_termList, + folderCharset.get(), m_scope, db, headers, headersSize, &m_expressionTree, pResult); + return rv; +} + +NS_IMETHODIMP +nsMsgFilter::SetFilterList(nsIMsgFilterList *filterList) +{ + // doesn't hold a ref. + m_filterList = filterList; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetFilterList(nsIMsgFilterList **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_IF_ADDREF(*aResult = m_filterList); + return NS_OK; +} + +void nsMsgFilter::SetFilterScript(nsCString *fileName) +{ + m_scriptFileName = *fileName; +} + +nsresult nsMsgFilter::ConvertMoveOrCopyToFolderValue(nsIMsgRuleAction *filterAction, nsCString &moveValue) +{ + NS_ENSURE_ARG_POINTER(filterAction); + int16_t filterVersion = kFileVersion; + if (m_filterList) + m_filterList->GetVersion(&filterVersion); + if (filterVersion <= k60Beta1Version) + { + nsCOMPtr <nsIMsgFolder> rootFolder; + nsCString folderUri; + + m_filterList->GetFolder(getter_AddRefs(rootFolder)); + // if relative path starts with kImap, this is a move to folder on the same server + if (moveValue.Find(kImapPrefix) == 0) + { + int32_t prefixLen = PL_strlen(kImapPrefix); + nsAutoCString originalServerPath(Substring(moveValue, prefixLen)); + if (filterVersion == k45Version) + { + nsAutoString unicodeStr; + nsresult rv = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), + originalServerPath, + unicodeStr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CopyUTF16toMUTF7(unicodeStr, originalServerPath); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr <nsIMsgFolder> destIFolder; + if (rootFolder) + { + rootFolder->FindSubFolder(originalServerPath, getter_AddRefs(destIFolder)); + if (destIFolder) + { + destIFolder->GetURI(folderUri); + filterAction->SetTargetFolderUri(folderUri); + moveValue.Assign(folderUri); + } + } + } + else + { + // start off leaving the value the same. + filterAction->SetTargetFolderUri(moveValue); + nsresult rv = NS_OK; + nsCOMPtr <nsIMsgFolder> localMailRoot; + rootFolder->GetURI(folderUri); + // if the root folder is not imap, than the local mail root is the server root. + // otherwise, it's the migrated local folders. + if (!StringBeginsWith(folderUri, NS_LITERAL_CSTRING("imap:"))) + localMailRoot = rootFolder; + else + { + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIMsgIncomingServer> server; + rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + rv = server->GetRootFolder(getter_AddRefs(localMailRoot)); + } + if (NS_SUCCEEDED(rv) && localMailRoot) + { + nsCString localRootURI; + nsCOMPtr <nsIMsgFolder> destIMsgFolder; + nsCOMPtr <nsIMsgFolder> localMailRootMsgFolder = do_QueryInterface(localMailRoot); + localMailRoot->GetURI(localRootURI); + nsCString destFolderUri; + destFolderUri.Assign( localRootURI); + // need to remove ".sbd" from moveValue, and perhaps escape it. + int32_t offset = moveValue.Find(".sbd/"); + if (offset != -1) + moveValue.Cut(offset, 4); + +#ifdef XP_MACOSX + nsCString unescapedMoveValue; + MsgUnescapeString(moveValue, 0, unescapedMoveValue); + moveValue = unescapedMoveValue; +#endif + destFolderUri.Append('/'); + if (filterVersion == k45Version) + { + nsAutoString unicodeStr; + rv = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), + moveValue, unicodeStr); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_MsgEscapeEncodeURLPath(unicodeStr, moveValue); + } + destFolderUri.Append(moveValue); + localMailRootMsgFolder->GetChildWithURI (destFolderUri, true, false /*caseInsensitive*/, getter_AddRefs(destIMsgFolder)); + + if (destIMsgFolder) + { + destIMsgFolder->GetURI(folderUri); + filterAction->SetTargetFolderUri(folderUri); + moveValue.Assign(folderUri); + } + } + } + } + else + filterAction->SetTargetFolderUri(moveValue); + + return NS_OK; + // set m_action.m_value.m_folderUri +} + +NS_IMETHODIMP +nsMsgFilter::SaveToTextFile(nsIOutputStream *aStream) +{ + NS_ENSURE_ARG_POINTER(aStream); + if (m_unparseable) + { + uint32_t bytesWritten; + //we need to trim leading whitespaces before filing out + m_unparsedBuffer.Trim(kWhitespace, true /*leadingCharacters*/, false /*trailingCharacters*/); + return aStream->Write(m_unparsedBuffer.get(), m_unparsedBuffer.Length(), &bytesWritten); + } + nsresult err = m_filterList->WriteWstrAttr(nsIMsgFilterList::attribName, m_filterName.get(), aStream); + err = m_filterList->WriteBoolAttr(nsIMsgFilterList::attribEnabled, m_enabled, aStream); + err = m_filterList->WriteStrAttr(nsIMsgFilterList::attribDescription, m_description.get(), aStream); + err = m_filterList->WriteIntAttr(nsIMsgFilterList::attribType, m_type, aStream); + if (IsScript()) + err = m_filterList->WriteStrAttr(nsIMsgFilterList::attribScriptFile, m_scriptFileName.get(), aStream); + else + err = SaveRule(aStream); + return err; +} + +nsresult nsMsgFilter::SaveRule(nsIOutputStream *aStream) +{ + nsresult err = NS_OK; + nsCOMPtr<nsIMsgFilterList> filterList; + GetFilterList(getter_AddRefs(filterList)); + nsAutoCString actionFilingStr; + + uint32_t numActions; + err = GetActionCount(&numActions); + NS_ENSURE_SUCCESS(err, err); + + for (uint32_t index = 0; index < numActions; index++) + { + nsCOMPtr<nsIMsgRuleAction> action; + err = GetActionAt(index, getter_AddRefs(action)); + if (NS_FAILED(err) || !action) + continue; + + nsMsgRuleActionType actionType; + action->GetType(&actionType); + GetActionFilingStr(actionType, actionFilingStr); + + err = filterList->WriteStrAttr(nsIMsgFilterList::attribAction, actionFilingStr.get(), aStream); + NS_ENSURE_SUCCESS(err, err); + + switch(actionType) + { + case nsMsgFilterAction::MoveToFolder: + case nsMsgFilterAction::CopyToFolder: + { + nsCString imapTargetString; + action->GetTargetFolderUri(imapTargetString); + err = filterList->WriteStrAttr(nsIMsgFilterList::attribActionValue, imapTargetString.get(), aStream); + } + break; + case nsMsgFilterAction::ChangePriority: + { + nsMsgPriorityValue priorityValue; + action->GetPriority(&priorityValue); + nsAutoCString priority; + NS_MsgGetUntranslatedPriorityName(priorityValue, priority); + err = filterList->WriteStrAttr( + nsIMsgFilterList::attribActionValue, priority.get(), aStream); + } + break; + case nsMsgFilterAction::Label: + { + nsMsgLabelValue label; + action->GetLabel(&label); + err = filterList->WriteIntAttr(nsIMsgFilterList::attribActionValue, label, aStream); + } + break; + case nsMsgFilterAction::JunkScore: + { + int32_t junkScore; + action->GetJunkScore(&junkScore); + err = filterList->WriteIntAttr(nsIMsgFilterList::attribActionValue, junkScore, aStream); + } + break; + case nsMsgFilterAction::AddTag: + case nsMsgFilterAction::Reply: + case nsMsgFilterAction::Forward: + { + nsCString strValue; + action->GetStrValue(strValue); + // strValue is e-mail address + err = filterList->WriteStrAttr(nsIMsgFilterList::attribActionValue, strValue.get(), aStream); + } + break; + case nsMsgFilterAction::Custom: + { + nsAutoCString id; + action->GetCustomId(id); + err = filterList->WriteStrAttr(nsIMsgFilterList::attribCustomId, id.get(), aStream); + nsAutoCString strValue; + action->GetStrValue(strValue); + if (strValue.Length()) + err = filterList->WriteWstrAttr(nsIMsgFilterList::attribActionValue, + NS_ConvertUTF8toUTF16(strValue).get(), + aStream); + } + break; + + default: + break; + } + } + // and here the fun begins - file out term list... + nsAutoCString condition; + err = MsgTermListToString(m_termList, condition); + if (NS_SUCCEEDED(err)) + err = filterList->WriteStrAttr(nsIMsgFilterList::attribCondition, condition.get(), aStream); + return err; +} + +// for each action, this table encodes the filterTypes that support the action. +struct RuleActionsTableEntry +{ + nsMsgRuleActionType action; + const char* actionFilingStr; /* used for filing out filters, don't translate! */ +}; + +static struct RuleActionsTableEntry ruleActionsTable[] = +{ + { nsMsgFilterAction::MoveToFolder, "Move to folder"}, + { nsMsgFilterAction::CopyToFolder, "Copy to folder"}, + { nsMsgFilterAction::ChangePriority, "Change priority"}, + { nsMsgFilterAction::Delete, "Delete"}, + { nsMsgFilterAction::MarkRead, "Mark read"}, + { nsMsgFilterAction::KillThread, "Ignore thread"}, + { nsMsgFilterAction::KillSubthread, "Ignore subthread"}, + { nsMsgFilterAction::WatchThread, "Watch thread"}, + { nsMsgFilterAction::MarkFlagged, "Mark flagged"}, + { nsMsgFilterAction::Label, "Label"}, + { nsMsgFilterAction::Reply, "Reply"}, + { nsMsgFilterAction::Forward, "Forward"}, + { nsMsgFilterAction::StopExecution, "Stop execution"}, + { nsMsgFilterAction::DeleteFromPop3Server, "Delete from Pop3 server"}, + { nsMsgFilterAction::LeaveOnPop3Server, "Leave on Pop3 server"}, + { nsMsgFilterAction::JunkScore, "JunkScore"}, + { nsMsgFilterAction::FetchBodyFromPop3Server, "Fetch body from Pop3Server"}, + { nsMsgFilterAction::AddTag, "AddTag"}, + { nsMsgFilterAction::MarkUnread, "Mark unread"}, + { nsMsgFilterAction::Custom, "Custom"}, +}; + +static const unsigned int sNumActions = MOZ_ARRAY_LENGTH(ruleActionsTable); + +const char *nsMsgFilter::GetActionStr(nsMsgRuleActionType action) +{ + for (unsigned int i = 0; i < sNumActions; i++) + { + if (action == ruleActionsTable[i].action) + return ruleActionsTable[i].actionFilingStr; + } + return ""; +} +/*static */nsresult nsMsgFilter::GetActionFilingStr(nsMsgRuleActionType action, nsCString &actionStr) +{ + for (unsigned int i = 0; i < sNumActions; i++) + { + if (action == ruleActionsTable[i].action) + { + actionStr = ruleActionsTable[i].actionFilingStr; + return NS_OK; + } + } + return NS_ERROR_INVALID_ARG; +} + + +nsMsgRuleActionType nsMsgFilter::GetActionForFilingStr(nsCString &actionStr) +{ + for (unsigned int i = 0; i < sNumActions; i++) + { + if (actionStr.Equals(ruleActionsTable[i].actionFilingStr)) + return ruleActionsTable[i].action; + } + return nsMsgFilterAction::None; +} + +int16_t +nsMsgFilter::GetVersion() +{ + if (!m_filterList) return 0; + int16_t version; + m_filterList->GetVersion(&version); + return version; +} + +#ifdef DEBUG +void nsMsgFilter::Dump() +{ + nsAutoCString s; + LossyCopyUTF16toASCII(m_filterName, s); + printf("filter %s type = %c desc = %s\n", s.get(), m_type + '0', m_description.get()); +} +#endif diff --git a/mailnews/base/search/src/nsMsgFilter.h b/mailnews/base/search/src/nsMsgFilter.h new file mode 100644 index 000000000..077baa2ff --- /dev/null +++ b/mailnews/base/search/src/nsMsgFilter.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgFilter_H_ +#define _nsMsgFilter_H_ + +#include "nscore.h" +#include "nsISupports.h" +#include "nsIMsgFilter.h" +#include "nsIMsgSearchScopeTerm.h" +#include "nsMsgSearchBoolExpression.h" +#include "nsIDateTimeFormat.h" +#include "nsIMsgFilterCustomAction.h" + +class nsMsgRuleAction : public nsIMsgRuleAction +{ +public: + NS_DECL_ISUPPORTS + + nsMsgRuleAction(); + + NS_DECL_NSIMSGRULEACTION + +private: + virtual ~nsMsgRuleAction(); + + nsMsgRuleActionType m_type; + // this used to be a union - why bother? + nsMsgPriorityValue m_priority; /* priority to set rule to */ + nsMsgLabelValue m_label; /* label to set rule to */ + nsCString m_folderUri; + int32_t m_junkScore; /* junk score (or arbitrary int value?) */ + // arbitrary string value. Currently, email address to forward to + nsCString m_strValue; + nsCString m_customId; + nsCOMPtr<nsIMsgFilterCustomAction> m_customAction; +} ; + + +class nsMsgFilter : public nsIMsgFilter +{ +public: + NS_DECL_ISUPPORTS + + nsMsgFilter(); + + NS_DECL_NSIMSGFILTER + + nsMsgFilterTypeType GetType() {return m_type;} + void SetType(nsMsgFilterTypeType type) {m_type = type;} + bool GetEnabled() {return m_enabled;} + void SetFilterScript(nsCString *filterName); + + bool IsScript() {return (m_type & + (nsMsgFilterType::InboxJavaScript | + nsMsgFilterType::NewsJavaScript)) != 0;} + + // filing routines. + nsresult SaveRule(nsIOutputStream *aStream); + + int16_t GetVersion(); +#ifdef DEBUG + void Dump(); +#endif + + nsresult ConvertMoveOrCopyToFolderValue(nsIMsgRuleAction *filterAction, nsCString &relativePath); + static const char *GetActionStr(nsMsgRuleActionType action); + static nsresult GetActionFilingStr(nsMsgRuleActionType action, nsCString &actionStr); + static nsMsgRuleActionType GetActionForFilingStr(nsCString &actionStr); +protected: + + /* + * Reporting function for filtering success/failure. + * Logging has to be enabled for the message to appear. + */ + nsresult LogRuleHitGeneric(nsIMsgRuleAction *aFilterAction, + nsIMsgDBHdr *aMsgHdr, + nsresult aRcode, + const char *aErrmsg); + + virtual ~nsMsgFilter(); + + nsMsgFilterTypeType m_type; + nsString m_filterName; + nsCString m_scriptFileName; // iff this filter is a script. + nsCString m_description; + nsCString m_unparsedBuffer; + + bool m_enabled; + bool m_temporary; + bool m_unparseable; + nsIMsgFilterList *m_filterList; /* owning filter list */ + nsCOMPtr<nsISupportsArray> m_termList; /* linked list of criteria terms */ + nsCOMPtr<nsIMsgSearchScopeTerm> m_scope; /* default for mail rules is inbox, but news rules could + have a newsgroup - LDAP would be invalid */ + nsTArray<nsCOMPtr<nsIMsgRuleAction> > m_actionList; + nsMsgSearchBoolExpression *m_expressionTree; + nsCOMPtr<nsIDateTimeFormat> mDateFormatter; +}; + +#endif 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 ------------------ diff --git a/mailnews/base/search/src/nsMsgFilterList.h b/mailnews/base/search/src/nsMsgFilterList.h new file mode 100644 index 000000000..2bb441d38 --- /dev/null +++ b/mailnews/base/search/src/nsMsgFilterList.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgFilterList_H_ +#define _nsMsgFilterList_H_ + +#include "nscore.h" +#include "nsIMsgFolder.h" +#include "nsIMsgFilterList.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsIFile.h" +#include "nsIOutputStream.h" + +const int16_t kFileVersion = 9; +const int16_t kManualContextVersion = 9; +const int16_t k60Beta1Version = 7; +const int16_t k45Version = 6; + +//////////////////////////////////////////////////////////////////////////////////////// +// The Msg Filter List is an interface designed to make accessing filter lists +// easier. Clients typically open a filter list and either enumerate the filters, +// or add new filters, or change the order around... +// +//////////////////////////////////////////////////////////////////////////////////////// + +class nsIMsgFilter; +class nsMsgFilter; + +class nsMsgFilterList : public nsIMsgFilterList +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGFILTERLIST + + nsMsgFilterList(); + + nsresult Close(); + nsresult LoadTextFilters(nsIInputStream *aStream); + + bool m_temporaryList; + +protected: + virtual ~nsMsgFilterList(); + + nsresult ComputeArbitraryHeaders(); + nsresult SaveTextFilters(nsIOutputStream *aStream); + // file streaming methods + int ReadChar(nsIInputStream *aStream); + int SkipWhitespace(nsIInputStream *aStream); + bool StrToBool(nsCString &str); + int LoadAttrib(nsMsgFilterFileAttribValue &attrib, nsIInputStream *aStream); + const char *GetStringForAttrib(nsMsgFilterFileAttribValue attrib); + nsresult LoadValue(nsCString &value, nsIInputStream *aStream); + int16_t m_fileVersion; + bool m_loggingEnabled; + bool m_startWritingToBuffer; //tells us when to start writing one whole filter to m_unparsedBuffer + nsCOMPtr<nsIMsgFolder> m_folder; + nsMsgFilter *m_curFilter; // filter we're filing in or out(?) + nsCString m_filterFileName; + nsTArray<nsCOMPtr<nsIMsgFilter> > m_filters; + nsCString m_arbitraryHeaders; + nsCOMPtr<nsIFile> m_defaultFile; + nsCString m_unparsedFilterBuffer; //holds one entire filter unparsed + +private: + nsresult TruncateLog(); + nsresult GetLogFile(nsIFile **aFile); + nsresult EnsureLogFile(nsIFile *file); + nsCOMPtr<nsIOutputStream> m_logStream; +}; + +#endif diff --git a/mailnews/base/search/src/nsMsgFilterService.cpp b/mailnews/base/search/src/nsMsgFilterService.cpp new file mode 100644 index 000000000..c8f52de5a --- /dev/null +++ b/mailnews/base/search/src/nsMsgFilterService.cpp @@ -0,0 +1,1216 @@ +/* -*- 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 nsMsgFilterService interface + +#include "msgCore.h" +#include "nsMsgFilterService.h" +#include "nsMsgFilterList.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIPrompt.h" +#include "nsIDocShell.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIStringBundle.h" +#include "nsIMsgSearchNotify.h" +#include "nsIUrlListener.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsIDBFolderInfo.h" +#include "nsIRDFService.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgCopyService.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsIMsgComposeService.h" +#include "nsMsgCompCID.h" +#include "nsNetUtil.h" +#include "nsMsgUtils.h" +#include "nsIMutableArray.h" +#include "nsIMsgMailSession.h" +#include "nsArrayUtils.h" +#include "nsCOMArray.h" +#include "nsIMsgFilterCustomAction.h" +#include "nsArrayEnumerator.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgWindow.h" +#include "nsIMsgSearchCustomTerm.h" +#include "nsIMsgSearchTerm.h" +#include "nsIMsgThread.h" +#include "nsAutoPtr.h" +#include "nsIMsgFilter.h" +#include "nsIMsgOperationListener.h" +#include "mozilla/Attributes.h" + +#define BREAK_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) { \ + NS_WARNING(_text); \ + mFinalResult = _rv; \ + break; \ +} + +#define CONTINUE_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) { \ + NS_WARNING(_text); \ + mFinalResult = _rv; \ + if (m_msgWindow && !ContinueExecutionPrompt()) \ + return OnEndExecution(); \ + continue; \ +} + +#define BREAK_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) { \ + NS_WARNING(_text); \ + mFinalResult = NS_ERROR_FAILURE; \ + break; \ +} + +#define CONTINUE_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) { \ + NS_WARNING(_text); \ + mFinalResult = NS_ERROR_FAILURE; \ + if (m_msgWindow && !ContinueExecutionPrompt()) \ + return OnEndExecution(); \ + continue; \ +} + +NS_IMPL_ISUPPORTS(nsMsgFilterService, nsIMsgFilterService) + +nsMsgFilterService::nsMsgFilterService() +{ +} + +nsMsgFilterService::~nsMsgFilterService() +{ +} + +NS_IMETHODIMP nsMsgFilterService::OpenFilterList(nsIFile *aFilterFile, + nsIMsgFolder *rootFolder, + nsIMsgWindow *aMsgWindow, + nsIMsgFilterList **resultFilterList) +{ + NS_ENSURE_ARG_POINTER(aFilterFile); + NS_ENSURE_ARG_POINTER(resultFilterList); + + bool exists = false; + nsresult rv = aFilterFile->Exists(&exists); + if (NS_FAILED(rv) || !exists) + { + rv = aFilterFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIInputStream> fileStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), aFilterFile); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(fileStream, NS_ERROR_OUT_OF_MEMORY); + + RefPtr<nsMsgFilterList> filterList = new nsMsgFilterList(); + NS_ENSURE_TRUE(filterList, NS_ERROR_OUT_OF_MEMORY); + filterList->SetFolder(rootFolder); + + // temporarily tell the filter where its file path is + filterList->SetDefaultFile(aFilterFile); + + int64_t size = 0; + rv = aFilterFile->GetFileSize(&size); + if (NS_SUCCEEDED(rv) && size > 0) + rv = filterList->LoadTextFilters(fileStream); + fileStream->Close(); + fileStream = nullptr; + if (NS_SUCCEEDED(rv)) + { + int16_t version; + filterList->GetVersion(&version); + if (version != kFileVersion) + SaveFilterList(filterList, aFilterFile); + } + else + { + if (rv == NS_MSG_FILTER_PARSE_ERROR && aMsgWindow) + { + rv = BackUpFilterFile(aFilterFile, aMsgWindow); + NS_ENSURE_SUCCESS(rv, rv); + rv = aFilterFile->SetFileSize(0); + NS_ENSURE_SUCCESS(rv, rv); + return OpenFilterList(aFilterFile, rootFolder, aMsgWindow, resultFilterList); + } + else if (rv == NS_MSG_CUSTOM_HEADERS_OVERFLOW && aMsgWindow) + ThrowAlertMsg("filterCustomHeaderOverflow", aMsgWindow); + else if (rv == NS_MSG_INVALID_CUSTOM_HEADER && aMsgWindow) + ThrowAlertMsg("invalidCustomHeader", aMsgWindow); + } + + NS_ADDREF(*resultFilterList = filterList); + return rv; +} + +NS_IMETHODIMP nsMsgFilterService::CloseFilterList(nsIMsgFilterList *filterList) +{ + //NS_ASSERTION(false,"CloseFilterList doesn't do anything yet"); + return NS_OK; +} + +/* save without deleting */ +NS_IMETHODIMP nsMsgFilterService::SaveFilterList(nsIMsgFilterList *filterList, nsIFile *filterFile) +{ + NS_ENSURE_ARG_POINTER(filterFile); + NS_ENSURE_ARG_POINTER(filterList); + + nsCOMPtr<nsIOutputStream> strm; + nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(strm), + filterFile, -1, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + rv = filterList->SaveToFile(strm); + + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(strm); + NS_ASSERTION(safeStream, "expected a safe output stream!"); + if (safeStream) { + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save filter file! possible data loss"); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgFilterService::CancelFilterList(nsIMsgFilterList *filterList) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsMsgFilterService::BackUpFilterFile(nsIFile *aFilterFile, nsIMsgWindow *aMsgWindow) +{ + AlertBackingUpFilterFile(aMsgWindow); + + nsCOMPtr<nsIFile> localParentDir; + nsresult rv = aFilterFile->GetParent(getter_AddRefs(localParentDir)); + NS_ENSURE_SUCCESS(rv,rv); + + //if back-up file exists delete the back up file otherwise copy fails. + nsCOMPtr <nsIFile> backupFile; + rv = localParentDir->Clone(getter_AddRefs(backupFile)); + NS_ENSURE_SUCCESS(rv,rv); + backupFile->AppendNative(NS_LITERAL_CSTRING("rulesbackup.dat")); + bool exists; + backupFile->Exists(&exists); + if (exists) + backupFile->Remove(false); + + return aFilterFile->CopyToNative(localParentDir, NS_LITERAL_CSTRING("rulesbackup.dat")); +} + +nsresult nsMsgFilterService::AlertBackingUpFilterFile(nsIMsgWindow *aMsgWindow) +{ + return ThrowAlertMsg("filterListBackUpMsg", aMsgWindow); +} + +nsresult //Do not use this routine if you have to call it very often because it creates a new bundle each time +nsMsgFilterService::GetStringFromBundle(const char *aMsgName, char16_t **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr <nsIStringBundle> bundle; + nsresult rv = GetFilterStringBundle(getter_AddRefs(bundle)); + if (NS_SUCCEEDED(rv) && bundle) + rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMsgName).get(), aResult); + return rv; + +} + +nsresult +nsMsgFilterService::GetFilterStringBundle(nsIStringBundle **aBundle) +{ + NS_ENSURE_ARG_POINTER(aBundle); + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + if (bundleService) + bundleService->CreateBundle("chrome://messenger/locale/filter.properties", + getter_AddRefs(bundle)); + NS_IF_ADDREF(*aBundle = bundle); + return NS_OK; +} + +nsresult +nsMsgFilterService::ThrowAlertMsg(const char*aMsgName, nsIMsgWindow *aMsgWindow) +{ + nsString alertString; + nsresult rv = GetStringFromBundle(aMsgName, getter_Copies(alertString)); + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryInterface(aMsgWindow)); + if (!msgWindow) { + nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + } + + if (NS_SUCCEEDED(rv) && !alertString.IsEmpty() && msgWindow) + { + nsCOMPtr <nsIDocShell> docShell; + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) + { + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell)); + if (dialog && !alertString.IsEmpty()) + dialog->Alert(nullptr, alertString.get()); + } + } + return rv; +} + +// this class is used to run filters after the fact, i.e., after new mail has been downloaded from the server. +// It can do the following: +// 1. Apply a single imap or pop3 filter on a single folder. +// 2. Apply multiple filters on a single imap or pop3 folder. +// 3. Apply a single filter on multiple imap or pop3 folders in the same account. +// 4. Apply multiple filters on multiple imap or pop3 folders in the same account. +// This will be called from the front end js code in the case of the apply filters to folder menu code, +// and from the filter dialog js code with the run filter now command. + + +// this class holds the list of filters and folders, and applies them in turn, first iterating +// over all the filters on one folder, and then advancing to the next folder and repeating. +// For each filter,we take the filter criteria and create a search term list. Then, we execute the search. +// We are a search listener so that we can build up the list of search hits. +// Then, when the search is done, we will apply the filter action(s) en-masse, so, for example, if the action is a move, +// we calls one method to move all the messages to the destination folder. Or, mark all the messages read. +// In the case of imap operations, or imap/local moves, the action will be asynchronous, so we'll need to be a url listener +// as well, and kick off the next filter when the action completes. +class nsMsgFilterAfterTheFact : public nsIUrlListener, public nsIMsgSearchNotify, public nsIMsgCopyServiceListener +{ +public: + nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow, + nsIMsgFilterList *aFilterList, nsIArray *aFolderList, + nsIMsgOperationListener *aCallback); + NS_DECL_ISUPPORTS + NS_DECL_NSIURLLISTENER + NS_DECL_NSIMSGSEARCHNOTIFY + NS_DECL_NSIMSGCOPYSERVICELISTENER + + nsresult AdvanceToNextFolder(); // kicks off the process +protected: + virtual ~nsMsgFilterAfterTheFact(); + virtual nsresult RunNextFilter(); + /** + * apply filter actions to current search hits + */ + nsresult ApplyFilter(); + nsresult OnEndExecution(); // do what we have to do to cleanup. + bool ContinueExecutionPrompt(); + nsresult DisplayConfirmationPrompt(nsIMsgWindow *msgWindow, const char16_t *confirmString, bool *confirmed); + nsCOMPtr<nsIMsgWindow> m_msgWindow; + nsCOMPtr<nsIMsgFilterList> m_filters; + nsCOMPtr<nsIArray> m_folders; + nsCOMPtr<nsIMsgFolder> m_curFolder; + nsCOMPtr<nsIMsgDatabase> m_curFolderDB; + nsCOMPtr<nsIMsgFilter> m_curFilter; + uint32_t m_curFilterIndex; + uint32_t m_curFolderIndex; + uint32_t m_numFilters; + uint32_t m_numFolders; + nsTArray<nsMsgKey> m_searchHits; + nsCOMPtr<nsIMutableArray> m_searchHitHdrs; + nsTArray<nsMsgKey> m_stopFiltering; + nsCOMPtr<nsIMsgSearchSession> m_searchSession; + nsCOMPtr<nsIMsgOperationListener> m_callback; + uint32_t m_nextAction; // next filter action to perform + nsresult mFinalResult; // report of overall success or failure + bool mNeedsRelease; // Did we need to release ourself? +}; + +NS_IMPL_ISUPPORTS(nsMsgFilterAfterTheFact, nsIUrlListener, nsIMsgSearchNotify, nsIMsgCopyServiceListener) + +nsMsgFilterAfterTheFact::nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow, + nsIMsgFilterList *aFilterList, + nsIArray *aFolderList, + nsIMsgOperationListener *aCallback) +{ + m_curFilterIndex = m_curFolderIndex = m_nextAction = 0; + m_msgWindow = aMsgWindow; + m_filters = aFilterList; + m_folders = aFolderList; + m_filters->GetFilterCount(&m_numFilters); + m_folders->GetLength(&m_numFolders); + + NS_ADDREF(this); // we own ourselves, and will release ourselves when execution is done. + mNeedsRelease = true; + + m_searchHitHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID); + m_callback = aCallback; + mFinalResult = NS_OK; +} + +nsMsgFilterAfterTheFact::~nsMsgFilterAfterTheFact() +{ +} + +// do what we have to do to cleanup. +nsresult nsMsgFilterAfterTheFact::OnEndExecution() +{ + if (m_searchSession) + m_searchSession->UnregisterListener(this); + + if (m_filters) + (void)m_filters->FlushLogIfNecessary(); + + if (m_callback) + (void)m_callback->OnStopOperation(mFinalResult); + + nsresult rv = mFinalResult; + // OnEndExecution() can be called a second time when a rule execution fails + // and the user is prompted whether he wants to continue. + if (mNeedsRelease) + { + Release(); // release ourselves. + mNeedsRelease = false; + } + return rv; +} + +nsresult nsMsgFilterAfterTheFact::RunNextFilter() +{ + nsresult rv = NS_OK; + while (true) + { + m_curFilter = nullptr; + if (m_curFilterIndex >= m_numFilters) + break; + BREAK_IF_FALSE(m_filters, "Missing filters"); + rv = m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter)); + CONTINUE_IF_FAILURE(rv, "Could not get filter at index"); + + nsCOMPtr <nsISupportsArray> searchTerms; + rv = m_curFilter->GetSearchTerms(getter_AddRefs(searchTerms)); + CONTINUE_IF_FAILURE(rv, "Could not get searchTerms"); + + if (m_searchSession) + m_searchSession->UnregisterListener(this); + m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv); + BREAK_IF_FAILURE(rv, "Failed to get search session"); + + nsMsgSearchScopeValue searchScope = nsMsgSearchScope::offlineMail; + uint32_t termCount; + searchTerms->Count(&termCount); + for (uint32_t termIndex = 0; termIndex < termCount; termIndex++) + { + nsCOMPtr <nsIMsgSearchTerm> term; + nsresult rv = searchTerms->QueryElementAt(termIndex, NS_GET_IID(nsIMsgSearchTerm), getter_AddRefs(term)); + BREAK_IF_FAILURE(rv, "Could not get search term"); + rv = m_searchSession->AppendTerm(term); + BREAK_IF_FAILURE(rv, "Could not append search term"); + } + CONTINUE_IF_FAILURE(rv, "Failed to setup search terms"); + m_searchSession->RegisterListener(this, + nsIMsgSearchSession::allNotifications); + + rv = m_searchSession->AddScopeTerm(searchScope, m_curFolder); + CONTINUE_IF_FAILURE(rv, "Failed to add scope term"); + m_nextAction = 0; + rv = m_searchSession->Search(m_msgWindow); + CONTINUE_IF_FAILURE(rv, "Search failed"); + return NS_OK; // OnSearchDone will continue + } + m_curFilter = nullptr; + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Search failed"); + return AdvanceToNextFolder(); +} + +nsresult nsMsgFilterAfterTheFact::AdvanceToNextFolder() +{ + nsresult rv = NS_OK; + // Advance through folders, making sure m_curFolder is null on errors + while (true) + { + m_stopFiltering.Clear(); + m_curFolder = nullptr; + if (m_curFolderIndex >= m_numFolders) + // final end of nsMsgFilterAfterTheFact object + return OnEndExecution(); + + // reset the filter index to apply all filters to this new folder + m_curFilterIndex = 0; + m_nextAction = 0; + rv = m_folders->QueryElementAt(m_curFolderIndex++, NS_GET_IID(nsIMsgFolder), getter_AddRefs(m_curFolder)); + CONTINUE_IF_FAILURE(rv, "Could not get next folder"); + + // Note: I got rv = NS_OK but null m_curFolder after deleting a folder + // outside of TB, when I select a single message and "run filter on message" + // and the filter is to move the message to the deleted folder. + + // m_curFolder may be null when the folder is deleted externally. + CONTINUE_IF_FALSE(m_curFolder, "Next folder returned null"); + + rv = m_curFolder->GetMsgDatabase(getter_AddRefs(m_curFolderDB)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder, &rv); + if (NS_SUCCEEDED(rv) && localFolder) + // will continue with OnStopRunningUrl + return localFolder->ParseFolder(m_msgWindow, this); + } + CONTINUE_IF_FAILURE(rv, "Could not get folder db"); + + rv = RunNextFilter(); + // RunNextFilter returns success when either filters are done, or an async process has started. + // It will call AdvanceToNextFolder itself if possible, so no need to call here. + BREAK_IF_FAILURE(rv, "Failed to run next filter"); + break; + } + return rv; +} + +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStartRunningUrl(nsIURI *aUrl) +{ + return NS_OK; +} + +// This is the return from a folder parse +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode) +{ + if (NS_SUCCEEDED(aExitCode)) + return RunNextFilter(); + + mFinalResult = aExitCode; + // If m_msgWindow then we are in a context where the user can deal with + // errors. Put up a prompt, and exit if user wants. + if (m_msgWindow && !ContinueExecutionPrompt()) + return OnEndExecution(); + + // folder parse failed, so stop processing this folder. + return AdvanceToNextFolder(); +} + +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchHit(nsIMsgDBHdr *header, nsIMsgFolder *folder) +{ + NS_ENSURE_ARG_POINTER(header); + NS_ENSURE_TRUE(m_searchHitHdrs, NS_ERROR_NOT_INITIALIZED); + + nsMsgKey msgKey; + header->GetMessageKey(&msgKey); + + // Under various previous actions (a move, delete, or stopExecution) + // we do not want to process filters on a per-message basis. + if (m_stopFiltering.Contains(msgKey)) + return NS_OK; + + m_searchHits.AppendElement(msgKey); + m_searchHitHdrs->AppendElement(header, false); + return NS_OK; +} + +// Continue after an async operation. +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchDone(nsresult status) +{ + if (NS_SUCCEEDED(status)) + return m_searchHits.IsEmpty() ? RunNextFilter() : ApplyFilter(); + + mFinalResult = status; + if (m_msgWindow && !ContinueExecutionPrompt()) + return OnEndExecution(); + + // The search failed, so move on to the next filter. + return RunNextFilter(); +} + +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnNewSearch() +{ + m_searchHits.Clear(); + m_searchHitHdrs->Clear(); + return NS_OK; +} + +// This method will apply filters. It will continue to advance though headers, +// filters, and folders until done, unless it starts an async operation with +// a callback. The callback should call ApplyFilter again. It only returns +// an error if it is impossible to continue after attempting to continue the +// next filter action, filter, or folder. +nsresult nsMsgFilterAfterTheFact::ApplyFilter() +{ + nsresult rv; + do { // error management block, break if unable to continue with filter. + if (!m_curFilter) + break; // Maybe not an error, we just need to call RunNextFilter(); + if (!m_curFolder) + break; // Maybe not an error, we just need to call AdvanceToNextFolder(); + BREAK_IF_FALSE(m_searchHitHdrs, "No search headers object"); + // we're going to log the filter actions before firing them because some actions are async + bool loggingEnabled = false; + if (m_filters) + (void)m_filters->GetLoggingEnabled(&loggingEnabled); + + nsCOMPtr<nsIArray> actionList; + rv = m_curFilter->GetSortedActionList(getter_AddRefs(actionList)); + BREAK_IF_FAILURE(rv, "Could not get action list for filter"); + + uint32_t numActions; + actionList->GetLength(&numActions); + + // We start from m_nextAction to allow us to continue applying actions + // after the return from an async copy. + while (m_nextAction < numActions) + { + nsCOMPtr<nsIMsgRuleAction>filterAction(do_QueryElementAt(actionList, m_nextAction++, &rv)); + CONTINUE_IF_FAILURE(rv, "actionList cannot QI element"); + + nsMsgRuleActionType actionType; + rv = filterAction->GetType(&actionType); + CONTINUE_IF_FAILURE(rv, "Could not get type for filter action"); + + nsCString actionTargetFolderUri; + if (actionType == nsMsgFilterAction::MoveToFolder || + actionType == nsMsgFilterAction::CopyToFolder) + { + rv = filterAction->GetTargetFolderUri(actionTargetFolderUri); + CONTINUE_IF_FALSE(NS_SUCCEEDED(rv) && !actionTargetFolderUri.IsEmpty(), + "actionTargetFolderUri is empty"); + } + + if (loggingEnabled) + { + for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr)); + if (msgHdr) + (void)m_curFilter->LogRuleHit(filterAction, msgHdr); + else + NS_WARNING("could not QI element to nsIMsgDBHdr"); + } + } + + // all actions that pass "this" as a listener in order to chain filter execution + // when the action is finished need to return before reaching the bottom of this + // routine, because we run the next filter at the end of this routine. + switch (actionType) + { + case nsMsgFilterAction::Delete: + // we can't pass ourselves in as a copy service listener because the copy service + // listener won't get called in several situations (e.g., the delete model is imap delete) + // and we rely on the listener getting called to continue the filter application. + // This means we're going to end up firing off the delete, and then subsequently + // issuing a search for the next filter, which will block until the delete finishes. + m_curFolder->DeleteMessages(m_searchHitHdrs, m_msgWindow, false, false, nullptr, false /*allow Undo*/ ); + + // don't allow any more filters on this message + m_stopFiltering.AppendElements(m_searchHits); + for (uint32_t i = 0; i < m_searchHits.Length(); i++) + m_curFolder->OrProcessingFlags(m_searchHits[i], nsMsgProcessingFlags::FilterToMove); + //if we are deleting then we couldn't care less about applying remaining filter actions + m_nextAction = numActions; + break; + + case nsMsgFilterAction::MoveToFolder: + // Even if move fails we will not run additional actions, as they + // would not have run if move succeeded. + m_nextAction = numActions; + // Fall through to the copy case. + MOZ_FALLTHROUGH; + case nsMsgFilterAction::CopyToFolder: + { + nsCString uri; + m_curFolder->GetURI(uri); + if (!actionTargetFolderUri.IsEmpty() && + !uri.Equals(actionTargetFolderUri)) + { + nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv); + nsCOMPtr<nsIRDFResource> res; + rv = rdf->GetResource(actionTargetFolderUri, getter_AddRefs(res)); + CONTINUE_IF_FAILURE(rv, "Could not get resource for action folder"); + + nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &rv)); + CONTINUE_IF_FAILURE(rv, "Could not QI resource to folder"); + + bool canFileMessages = true; + nsCOMPtr<nsIMsgFolder> parentFolder; + destIFolder->GetParent(getter_AddRefs(parentFolder)); + if (parentFolder) + destIFolder->GetCanFileMessages(&canFileMessages); + if (!parentFolder || !canFileMessages) + { + m_curFilter->SetEnabled(false); + destIFolder->ThrowAlertMsg("filterDisabled",m_msgWindow); + // we need to explicitly save the filter file. + m_filters->SaveToDefaultFile(); + // In the case of applying multiple filters + // we might want to remove the filter from the list, but + // that's a bit evil since we really don't know that we own + // the list. Disabling it doesn't do a lot of good since + // we still apply disabled filters. Currently, we don't + // have any clients that apply filters to multiple folders, + // so this might be the edge case of an edge case. + m_nextAction = numActions; + mFinalResult = NS_ERROR_FAILURE; + break; + } + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + CONTINUE_IF_FAILURE(rv, "Could not get copy service") + + if (actionType == nsMsgFilterAction::MoveToFolder) + { + m_stopFiltering.AppendElements(m_searchHits); + for (uint32_t i = 0; i < m_searchHits.Length(); i++) + m_curFolder->OrProcessingFlags(m_searchHits[i], + nsMsgProcessingFlags::FilterToMove); + } + + rv = copyService->CopyMessages(m_curFolder, m_searchHitHdrs, + destIFolder, actionType == nsMsgFilterAction::MoveToFolder, + this, m_msgWindow, false); + CONTINUE_IF_FAILURE(rv, "CopyMessages failed"); + return NS_OK; // OnStopCopy callback to continue; + } + else + NS_WARNING("Move or copy failed, empty or unchanged destination"); + } + break; + case nsMsgFilterAction::MarkRead: + // crud, no listener support here - we'll probably just need to go on and apply + // the next filter, and, in the imap case, rely on multiple connection and url + // queueing to stay out of trouble + m_curFolder->MarkMessagesRead(m_searchHitHdrs, true); + break; + case nsMsgFilterAction::MarkUnread: + m_curFolder->MarkMessagesRead(m_searchHitHdrs, false); + break; + case nsMsgFilterAction::MarkFlagged: + m_curFolder->MarkMessagesFlagged(m_searchHitHdrs, true); + break; + case nsMsgFilterAction::KillThread: + case nsMsgFilterAction::WatchThread: + { + for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr)); + CONTINUE_IF_FALSE(msgHdr, "Could not get msg header"); + + nsCOMPtr<nsIMsgThread> msgThread; + nsMsgKey threadKey; + m_curFolderDB->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(msgThread)); + CONTINUE_IF_FALSE(msgThread, "Could not find msg thread"); + msgThread->GetThreadKey(&threadKey); + if (actionType == nsMsgFilterAction::KillThread) + m_curFolderDB->MarkThreadIgnored(msgThread, threadKey, true, nullptr); + else + m_curFolderDB->MarkThreadWatched(msgThread, threadKey, true, nullptr); + } + } + break; + case nsMsgFilterAction::KillSubthread: + { + for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr)); + CONTINUE_IF_FALSE(msgHdr, "Could not get msg header"); + m_curFolderDB->MarkHeaderKilled(msgHdr, true, nullptr); + } + } + break; + case nsMsgFilterAction::ChangePriority: + { + nsMsgPriorityValue filterPriority; + filterAction->GetPriority(&filterPriority); + for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr)); + CONTINUE_IF_FALSE(msgHdr, "Could not get msg header"); + msgHdr->SetPriority(filterPriority); + } + } + break; + case nsMsgFilterAction::Label: + { + nsMsgLabelValue filterLabel; + filterAction->GetLabel(&filterLabel); + m_curFolder->SetLabelForMessages(m_searchHitHdrs, filterLabel); + } + break; + case nsMsgFilterAction::AddTag: + { + nsCString keyword; + filterAction->GetStrValue(keyword); + m_curFolder->AddKeywordsToMessages(m_searchHitHdrs, keyword); + } + break; + case nsMsgFilterAction::JunkScore: + { + nsAutoCString junkScoreStr; + int32_t junkScore; + filterAction->GetJunkScore(&junkScore); + junkScoreStr.AppendInt(junkScore); + m_curFolder->SetJunkScoreForMessages(m_searchHitHdrs, junkScoreStr); + } + break; + case nsMsgFilterAction::Forward: + { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = m_curFolder->GetServer(getter_AddRefs(server)); + CONTINUE_IF_FAILURE(rv, "Could not get server"); + nsCString forwardTo; + filterAction->GetStrValue(forwardTo); + CONTINUE_IF_FALSE(!forwardTo.IsEmpty(), "blank forwardTo URI"); + nsCOMPtr<nsIMsgComposeService> compService = + do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv); + CONTINUE_IF_FAILURE(rv, "Could not get compose service"); + + for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryElementAt(m_searchHitHdrs, + msgIndex)); + if (msgHdr) + rv = compService->ForwardMessage(NS_ConvertASCIItoUTF16(forwardTo), + msgHdr, m_msgWindow, server, + nsIMsgComposeService::kForwardAsDefault); + CONTINUE_IF_FALSE(msgHdr && NS_SUCCEEDED(rv), "Forward action failed"); + } + } + break; + case nsMsgFilterAction::Reply: + { + nsCString replyTemplateUri; + filterAction->GetStrValue(replyTemplateUri); + CONTINUE_IF_FALSE(!replyTemplateUri.IsEmpty(), "Empty reply template URI"); + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = m_curFolder->GetServer(getter_AddRefs(server)); + CONTINUE_IF_FAILURE(rv, "Could not get server"); + + nsCOMPtr<nsIMsgComposeService> compService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv); + CONTINUE_IF_FAILURE(rv, "Could not get compose service"); + for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr)); + CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr"); + rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri.get(), m_msgWindow, server); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_ABORT) { + m_curFilter->LogRuleHitFail(filterAction, msgHdr, rv, "Sending reply aborted"); + } else { + m_curFilter->LogRuleHitFail(filterAction, msgHdr, rv, "Error sending reply"); + } + } + CONTINUE_IF_FAILURE(rv, "ReplyWithTemplate failed"); + } + } + break; + case nsMsgFilterAction::DeleteFromPop3Server: + { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder); + CONTINUE_IF_FALSE(localFolder, "Current folder not a local folder"); + // This action ignores the deleteMailLeftOnServer preference + rv = localFolder->MarkMsgsOnPop3Server(m_searchHitHdrs, POP3_FORCE_DEL); + CONTINUE_IF_FAILURE(rv, "MarkMsgsOnPop3Server failed"); + + nsCOMPtr<nsIMutableArray> partialMsgs; + // Delete the partial headers. They're useless now + // that the server copy is being deleted. + for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr)); + CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr"); + uint32_t flags; + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Partial) + { + if (!partialMsgs) + partialMsgs = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + CONTINUE_IF_FALSE(partialMsgs, "Could not create partialMsgs array"); + partialMsgs->AppendElement(msgHdr, false); + m_stopFiltering.AppendElement(m_searchHits[msgIndex]); + m_curFolder->OrProcessingFlags(m_searchHits[msgIndex], + nsMsgProcessingFlags::FilterToMove); + } + } + if (partialMsgs) + { + m_curFolder->DeleteMessages(partialMsgs, m_msgWindow, true, false, nullptr, false); + CONTINUE_IF_FAILURE(rv, "Delete messages failed"); + } + } + break; + case nsMsgFilterAction::FetchBodyFromPop3Server: + { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder); + CONTINUE_IF_FALSE(localFolder, "current folder not local"); + nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + CONTINUE_IF_FAILURE(rv, "Could not create messages array"); + for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr)); + CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr"); + uint32_t flags = 0; + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Partial) + messages->AppendElement(msgHdr, false); + } + uint32_t msgsToFetch; + messages->GetLength(&msgsToFetch); + if (msgsToFetch > 0) + { + rv = m_curFolder->DownloadMessagesForOffline(messages, m_msgWindow); + CONTINUE_IF_FAILURE(rv, "DownloadMessagesForOffline failed"); + } + } + break; + + case nsMsgFilterAction::StopExecution: + { + // don't apply any more filters + m_stopFiltering.AppendElements(m_searchHits); + m_nextAction = numActions; + } + break; + + case nsMsgFilterAction::Custom: + { + nsMsgFilterTypeType filterType; + m_curFilter->GetFilterType(&filterType); + nsCOMPtr<nsIMsgFilterCustomAction> customAction; + rv = filterAction->GetCustomAction(getter_AddRefs(customAction)); + CONTINUE_IF_FAILURE(rv, "Could not get custom action"); + + nsAutoCString value; + filterAction->GetStrValue(value); + bool isAsync = false; + customAction->GetIsAsync(&isAsync); + rv = customAction->Apply(m_searchHitHdrs, value, this, + filterType, m_msgWindow); + CONTINUE_IF_FAILURE(rv, "custom action failed to apply"); + if (isAsync) + return NS_OK; // custom action should call ApplyFilter on callback + } + break; + + default: + break; + } + } + } while (false); // end error management block + return RunNextFilter(); +} + +NS_IMETHODIMP nsMsgFilterService::GetTempFilterList(nsIMsgFolder *aFolder, nsIMsgFilterList **aFilterList) +{ + NS_ENSURE_ARG_POINTER(aFilterList); + + nsMsgFilterList *filterList = new nsMsgFilterList; + NS_ENSURE_TRUE(filterList, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*aFilterList = filterList); + (*aFilterList)->SetFolder(aFolder); + filterList->m_temporaryList = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilterService::ApplyFiltersToFolders(nsIMsgFilterList *aFilterList, + nsIArray *aFolders, + nsIMsgWindow *aMsgWindow, + nsIMsgOperationListener *aCallback) +{ + NS_ENSURE_ARG_POINTER(aFilterList); + NS_ENSURE_ARG_POINTER(aFolders); + + RefPtr<nsMsgFilterAfterTheFact> filterExecutor = + new nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolders, aCallback); + if (filterExecutor) + return filterExecutor->AdvanceToNextFolder(); + else + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgFilterService::AddCustomAction(nsIMsgFilterCustomAction *aAction) +{ + mCustomActions.AppendObject(aAction); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterService::GetCustomActions(nsISimpleEnumerator** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + return NS_NewArrayEnumerator(aResult, mCustomActions); +} + +NS_IMETHODIMP +nsMsgFilterService::GetCustomAction(const nsACString & aId, + nsIMsgFilterCustomAction** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + for (int32_t i = 0; i < mCustomActions.Count(); i++) + { + nsAutoCString id; + nsresult rv = mCustomActions[i]->GetId(id); + if (NS_SUCCEEDED(rv) && aId.Equals(id)) + { + NS_ADDREF(*aResult = mCustomActions[i]); + return NS_OK; + } + } + aResult = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgFilterService::AddCustomTerm(nsIMsgSearchCustomTerm *aTerm) +{ + mCustomTerms.AppendObject(aTerm); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterService::GetCustomTerms(nsISimpleEnumerator** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + return NS_NewArrayEnumerator(aResult, mCustomTerms); +} + +NS_IMETHODIMP +nsMsgFilterService::GetCustomTerm(const nsACString& aId, + nsIMsgSearchCustomTerm** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + for (int32_t i = 0; i < mCustomTerms.Count(); i++) + { + nsAutoCString id; + nsresult rv = mCustomTerms[i]->GetId(id); + if (NS_SUCCEEDED(rv) && aId.Equals(id)) + { + NS_ADDREF(*aResult = mCustomTerms[i]); + return NS_OK; + } + } + aResult = nullptr; + // we use a null result to indicate failure to find a term + return NS_OK; +} + +// nsMsgApplyFiltersToMessages overrides nsMsgFilterAfterTheFact in order to +// apply filters to a list of messages, rather than an entire folder +class nsMsgApplyFiltersToMessages : public nsMsgFilterAfterTheFact +{ +public: + nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow, + nsIMsgFilterList *aFilterList, + nsIArray *aFolderList, nsIArray *aMsgHdrList, + nsMsgFilterTypeType aFilterType, + nsIMsgOperationListener *aCallback); + +protected: + virtual nsresult RunNextFilter(); + + nsCOMArray<nsIMsgDBHdr> m_msgHdrList; + nsMsgFilterTypeType m_filterType; +}; + +nsMsgApplyFiltersToMessages::nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow, + nsIMsgFilterList *aFilterList, + nsIArray *aFolderList, + nsIArray *aMsgHdrList, + nsMsgFilterTypeType aFilterType, + nsIMsgOperationListener *aCallback) +: nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolderList, aCallback), + m_filterType(aFilterType) +{ + nsCOMPtr<nsISimpleEnumerator> msgEnumerator; + if (NS_SUCCEEDED(aMsgHdrList->Enumerate(getter_AddRefs(msgEnumerator)))) + { + uint32_t length; + if (NS_SUCCEEDED(aMsgHdrList->GetLength(&length))) + m_msgHdrList.SetCapacity(length); + + bool hasMore; + while (NS_SUCCEEDED(msgEnumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIMsgDBHdr> msgHdr; + if (NS_SUCCEEDED(msgEnumerator->GetNext(getter_AddRefs(supports))) && + (msgHdr = do_QueryInterface(supports))) + m_msgHdrList.AppendObject(msgHdr); + } + } +} + +nsresult nsMsgApplyFiltersToMessages::RunNextFilter() +{ + nsresult rv = NS_OK; + while (true) + { + m_curFilter = nullptr; // we are done with the current filter + if (!m_curFolder || // Not an error, we just need to run AdvanceToNextFolder() + m_curFilterIndex >= m_numFilters) + break; + BREAK_IF_FALSE(m_filters, "No filters"); + nsMsgFilterTypeType filterType; + bool isEnabled; + rv = m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter)); + CONTINUE_IF_FAILURE(rv, "Could not get filter"); + rv = m_curFilter->GetFilterType(&filterType); + CONTINUE_IF_FAILURE(rv, "Could not get filter type"); + if (!(filterType & m_filterType)) + continue; + rv = m_curFilter->GetEnabled(&isEnabled); + CONTINUE_IF_FAILURE(rv, "Could not get isEnabled"); + if (!isEnabled) + continue; + + nsCOMPtr<nsIMsgSearchScopeTerm> scope(new nsMsgSearchScopeTerm(nullptr, nsMsgSearchScope::offlineMail, m_curFolder)); + BREAK_IF_FALSE(scope, "Could not create scope, OOM?"); + m_curFilter->SetScope(scope); + OnNewSearch(); + + for (int32_t i = 0; i < m_msgHdrList.Count(); i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr = m_msgHdrList[i]; + CONTINUE_IF_FALSE(msgHdr, "null msgHdr"); + + bool matched; + rv = m_curFilter->MatchHdr(msgHdr, m_curFolder, m_curFolderDB, nullptr, 0, &matched); + if (NS_SUCCEEDED(rv) && matched) + { + // In order to work with nsMsgFilterAfterTheFact::ApplyFilter we initialize + // nsMsgFilterAfterTheFact's information with a search hit now for the message + // that we're filtering. + OnSearchHit(msgHdr, m_curFolder); + } + } + m_curFilter->SetScope(nullptr); + + if (m_searchHits.Length() > 0) + { + m_nextAction = 0; + rv = ApplyFilter(); + if (NS_SUCCEEDED(rv)) + return NS_OK; // async callback will continue, or we are done. + } + } + m_curFilter = nullptr; + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to run filters"); + // We expect the failure is already recorded through one of the macro + // expressions, that will have console logging added to them. + // So an additional console warning is not needed here. + return AdvanceToNextFolder(); +} + +NS_IMETHODIMP nsMsgFilterService::ApplyFilters(nsMsgFilterTypeType aFilterType, + nsIArray *aMsgHdrList, + nsIMsgFolder *aFolder, + nsIMsgWindow *aMsgWindow, + nsIMsgOperationListener *aCallback) +{ + NS_ENSURE_ARG_POINTER(aFolder); + + nsCOMPtr<nsIMsgFilterList> filterList; + nsresult rv = aFolder->GetFilterList(aMsgWindow, getter_AddRefs(filterList)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> folderList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + folderList->AppendElement(aFolder, false); + + // Create our nsMsgApplyFiltersToMessages object which will be called when ApplyFiltersToHdr + // finds one or more filters that hit. + RefPtr<nsMsgApplyFiltersToMessages> filterExecutor = + new nsMsgApplyFiltersToMessages(aMsgWindow, filterList, folderList, + aMsgHdrList, aFilterType, aCallback); + + if (filterExecutor) + return filterExecutor->AdvanceToNextFolder(); + + return NS_ERROR_OUT_OF_MEMORY; +} + +/* void OnStartCopy (); */ +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStartCopy() +{ + return NS_OK; +} + +/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */ +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnProgress(uint32_t aProgress, uint32_t aProgressMax) +{ + return NS_OK; +} + +/* void SetMessageKey (in uint32_t aKey); */ +NS_IMETHODIMP nsMsgFilterAfterTheFact::SetMessageKey(nsMsgKey /* aKey */) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterAfterTheFact::GetMessageId(nsACString& messageId) +{ + return NS_OK; +} + +/* void OnStopCopy (in nsresult aStatus); */ +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopCopy(nsresult aStatus) +{ + if (NS_SUCCEEDED(aStatus)) + return ApplyFilter(); + + mFinalResult = aStatus; + if (m_msgWindow && !ContinueExecutionPrompt()) + return OnEndExecution(); + + // Copy failed, so run the next filter + return RunNextFilter(); +} + +bool nsMsgFilterAfterTheFact::ContinueExecutionPrompt() +{ + if (!m_curFilter) + return false; + nsCOMPtr<nsIStringBundle> bundle; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) + return false; + bundleService->CreateBundle("chrome://messenger/locale/filter.properties", + getter_AddRefs(bundle)); + if (!bundle) + return false; + nsString filterName; + m_curFilter->GetFilterName(filterName); + nsString formatString; + nsString confirmText; + const char16_t *formatStrings[] = + { + filterName.get() + }; + nsresult rv = bundle->FormatStringFromName(u"continueFilterExecution", + formatStrings, 1, getter_Copies(confirmText)); + if (NS_FAILED(rv)) + return false; + bool returnVal = false; + (void) DisplayConfirmationPrompt(m_msgWindow, confirmText.get(), &returnVal); + return returnVal; +} + +nsresult +nsMsgFilterAfterTheFact::DisplayConfirmationPrompt(nsIMsgWindow *msgWindow, const char16_t *confirmString, bool *confirmed) +{ + if (msgWindow) + { + nsCOMPtr <nsIDocShell> docShell; + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) + { + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell)); + if (dialog && confirmString) + dialog->Confirm(nullptr, confirmString, confirmed); + } + } + return NS_OK; +} diff --git a/mailnews/base/search/src/nsMsgFilterService.h b/mailnews/base/search/src/nsMsgFilterService.h new file mode 100644 index 000000000..8fdc6a5cb --- /dev/null +++ b/mailnews/base/search/src/nsMsgFilterService.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgFilterService_H_ +#define _nsMsgFilterService_H_ + +#include "nsIMsgFilterService.h" +#include "nsCOMArray.h" + +class nsIMsgWindow; +class nsIStringBundle; + + + +// The filter service is used to acquire and manipulate filter lists. + +class nsMsgFilterService : public nsIMsgFilterService +{ + +public: + nsMsgFilterService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGFILTERSERVICE +/* clients call OpenFilterList to get a handle to a FilterList, of existing nsMsgFilter *. + These are manipulated by the front end as a result of user interaction + with dialog boxes. To apply the new list call MSG_CloseFilterList. +*/ + nsresult BackUpFilterFile(nsIFile *aFilterFile, nsIMsgWindow *aMsgWindow); + nsresult AlertBackingUpFilterFile(nsIMsgWindow *aMsgWindow); + nsresult ThrowAlertMsg(const char*aMsgName, nsIMsgWindow *aMsgWindow); + nsresult GetStringFromBundle(const char *aMsgName, char16_t **aResult); + nsresult GetFilterStringBundle(nsIStringBundle **aBundle); + +protected: + virtual ~nsMsgFilterService(); + + nsCOMArray<nsIMsgFilterCustomAction> mCustomActions; // defined custom action list + nsCOMArray<nsIMsgSearchCustomTerm> mCustomTerms; // defined custom term list + +}; + +#endif // _nsMsgFilterService_H_ + diff --git a/mailnews/base/search/src/nsMsgImapSearch.cpp b/mailnews/base/search/src/nsMsgImapSearch.cpp new file mode 100644 index 000000000..5b3b2698a --- /dev/null +++ b/mailnews/base/search/src/nsMsgImapSearch.cpp @@ -0,0 +1,1004 @@ +/* -*- Mode: C++; tab-width: 4; 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 "nsMsgSearchAdapter.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsMsgResultElement.h" +#include "nsMsgSearchTerm.h" +#include "nsIMsgHdr.h" +#include "nsMsgSearchImap.h" +#include "prmem.h" +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (disable : 4996) +#endif +#include "nsISupportsArray.h" +// Implementation of search for IMAP mail folders + + +nsMsgSearchOnlineMail::nsMsgSearchOnlineMail (nsMsgSearchScopeTerm *scope, nsISupportsArray *termList) : nsMsgSearchAdapter (scope, termList) +{ +} + + +nsMsgSearchOnlineMail::~nsMsgSearchOnlineMail () +{ +} + + +nsresult nsMsgSearchOnlineMail::ValidateTerms () +{ + nsresult err = nsMsgSearchAdapter::ValidateTerms (); + + if (NS_SUCCEEDED(err)) + { + // ### mwelch Figure out the charsets to use + // for the search terms and targets. + nsAutoString srcCharset, dstCharset; + GetSearchCharsets(srcCharset, dstCharset); + + // do IMAP specific validation + err = Encode (m_encoding, m_searchTerms, dstCharset.get()); + NS_ASSERTION(NS_SUCCEEDED(err), "failed to encode imap search"); + } + + return err; +} + + +NS_IMETHODIMP nsMsgSearchOnlineMail::GetEncoding (char **result) +{ + *result = ToNewCString(m_encoding); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchOnlineMail::AddResultElement (nsIMsgDBHdr *pHeaders) +{ + nsresult err = NS_OK; + + nsCOMPtr<nsIMsgSearchSession> searchSession; + m_scope->GetSearchSession(getter_AddRefs(searchSession)); + if (searchSession) + { + nsCOMPtr <nsIMsgFolder> scopeFolder; + err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + searchSession->AddSearchHit(pHeaders, scopeFolder); + } + //XXXX alecf do not checkin without fixing! m_scope->m_searchSession->AddResultElement (newResult); + return err; +} + +nsresult nsMsgSearchOnlineMail::Search (bool *aDone) +{ + // we should never end up here for a purely online + // folder. We might for an offline IMAP folder. + nsresult err = NS_ERROR_NOT_IMPLEMENTED; + + return err; +} + +nsresult nsMsgSearchOnlineMail::Encode (nsCString& pEncoding, + nsISupportsArray *searchTerms, + const char16_t *destCharset) +{ + nsCString imapTerms; + + //check if searchTerms are ascii only + bool asciiOnly = true; + // ### what's this mean in the NWO????? + + if (true) // !(srcCharset & CODESET_MASK == STATEFUL || srcCharset & CODESET_MASK == WIDECHAR) ) //assume all single/multiple bytes charset has ascii as subset + { + uint32_t termCount; + searchTerms->Count(&termCount); + uint32_t i = 0; + + for (i = 0; i < termCount && asciiOnly; i++) + { + nsCOMPtr<nsIMsgSearchTerm> pTerm; + searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm), + (void **)getter_AddRefs(pTerm)); + + nsMsgSearchAttribValue attribute; + pTerm->GetAttrib(&attribute); + if (IS_STRING_ATTRIBUTE(attribute)) + { + nsString pchar; + nsCOMPtr <nsIMsgSearchValue> searchValue; + + nsresult rv = pTerm->GetValue(getter_AddRefs(searchValue)); + if (NS_FAILED(rv) || !searchValue) + continue; + + + rv = searchValue->GetStr(pchar); + if (NS_FAILED(rv) || pchar.IsEmpty()) + continue; + asciiOnly = NS_IsAscii(pchar.get()); + } + } + } +// else +// asciiOnly = false; // TODO: enable this line when the condition is not a plain "true" in the if(). + + nsAutoString usAsciiCharSet(NS_LITERAL_STRING("us-ascii")); + // Get the optional CHARSET parameter, in case we need it. + char *csname = GetImapCharsetParam(asciiOnly ? usAsciiCharSet.get() : destCharset); + + // We do not need "srcCharset" since the search term in always unicode. + // I just pass destCharset for both src and dest charset instead of removing srcCharst from the arguemnt. + nsresult err = nsMsgSearchAdapter::EncodeImap (getter_Copies(imapTerms), searchTerms, + asciiOnly ? usAsciiCharSet.get(): destCharset, + asciiOnly ? usAsciiCharSet.get(): destCharset, false); + if (NS_SUCCEEDED(err)) + { + pEncoding.Append("SEARCH"); + if (csname) + pEncoding.Append(csname); + pEncoding.Append(imapTerms); + } + PR_FREEIF(csname); + return err; +} + + +nsresult +nsMsgSearchValidityManager::InitOfflineMailTable() +{ + NS_ASSERTION(!m_offlineMailTable, "offline mail table already initted"); + nsresult rv = NewTable (getter_AddRefs(m_offlineMailTable)); + NS_ENSURE_SUCCESS(rv,rv); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + // m_offlineMailTable->SetValidButNotShown (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsHigherThan, 1); + // m_offlineMailTable->SetValidButNotShown (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + return rv; +} + + +nsresult +nsMsgSearchValidityManager::InitOnlineMailTable() +{ + NS_ASSERTION(!m_onlineMailTable, "Online mail table already initted!"); + nsresult rv = NewTable (getter_AddRefs(m_onlineMailTable)); + NS_ENSURE_SUCCESS(rv,rv); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + return rv; +} + +nsresult +nsMsgSearchValidityManager::InitOnlineMailFilterTable() +{ + // Oh what a tangled web... + // + // IMAP filtering happens on the client, fundamentally using the same + // capabilities as POP filtering. However, since we don't yet have the + // IMAP message body, we can't filter on body attributes. So this table + // is supposed to be the same as offline mail, except that the body + // attribute is omitted + NS_ASSERTION(!m_onlineMailFilterTable, "online filter table already initted"); + nsresult rv = NewTable (getter_AddRefs(m_onlineMailFilterTable)); + NS_ENSURE_SUCCESS(rv,rv); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + return rv; +} + +nsresult +nsMsgSearchValidityManager::InitOfflineMailFilterTable() +{ + NS_ASSERTION(!m_offlineMailFilterTable, "offline mail filter table already initted"); + nsresult rv = NewTable (getter_AddRefs(m_offlineMailFilterTable)); + NS_ENSURE_SUCCESS(rv,rv); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + // junk status and attachment status not available for offline mail (POP) filters + // because we won't know those until after the message has been analyzed. + // see bug #185937 + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + return rv; +} + +// Online Manual is used for IMAP and NEWS, where at manual +// filtering we have junk info, but cannot assure that the +// body is available. +nsresult +nsMsgSearchValidityManager::InitOnlineManualFilterTable() +{ + NS_ASSERTION(!m_onlineManualFilterTable, "online manual filter table already initted"); + nsresult rv = NewTable(getter_AddRefs(m_onlineManualFilterTable)); + NS_ENSURE_SUCCESS(rv, rv); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + + // HasAttachmentStatus does not work reliably until the user has opened a + // message to force it through MIME. We need a solution for this (bug 105169) + // but in the meantime, I'm doing the same thing here that we do in the + // offline mail table, as this does not really depend at the moment on + // whether we have downloaded the body for offline use. + m_onlineManualFilterTable->SetAvailable (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + return rv; +} diff --git a/mailnews/base/search/src/nsMsgLocalSearch.cpp b/mailnews/base/search/src/nsMsgLocalSearch.cpp new file mode 100644 index 000000000..3ce510b6d --- /dev/null +++ b/mailnews/base/search/src/nsMsgLocalSearch.cpp @@ -0,0 +1,1022 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +// Implementation of db search for POP and offline IMAP mail folders + +#include "msgCore.h" +#include "nsIMsgDatabase.h" +#include "nsMsgSearchCore.h" +#include "nsMsgLocalSearch.h" +#include "nsIStreamListener.h" +#include "nsMsgSearchBoolExpression.h" +#include "nsMsgSearchTerm.h" +#include "nsMsgResultElement.h" +#include "nsIDBFolderInfo.h" +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (disable : 4996) +#endif +#include "nsISupportsArray.h" +#include "nsMsgBaseCID.h" +#include "nsMsgSearchValue.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgWindow.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFilterPlugin.h" +#include "nsMsgMessageFlags.h" +#include "nsMsgUtils.h" +#include "nsIMsgFolder.h" + +extern "C" +{ + extern int MK_MSG_SEARCH_STATUS; + extern int MK_MSG_CANT_SEARCH_IF_NO_SUMMARY; + extern int MK_MSG_SEARCH_HITS_NOT_IN_DB; +} + + +//---------------------------------------------------------------------------- +// Class definitions for the boolean expression structure.... +//---------------------------------------------------------------------------- + + +nsMsgSearchBoolExpression * nsMsgSearchBoolExpression::AddSearchTerm(nsMsgSearchBoolExpression * aOrigExpr, nsIMsgSearchTerm * aNewTerm, char * aEncodingStr) +// appropriately add the search term to the current expression and return a pointer to the +// new expression. The encodingStr is the IMAP/NNTP encoding string for newTerm. +{ + return aOrigExpr->leftToRightAddTerm(aNewTerm, aEncodingStr); +} + +nsMsgSearchBoolExpression * nsMsgSearchBoolExpression::AddExpressionTree(nsMsgSearchBoolExpression * aOrigExpr, nsMsgSearchBoolExpression * aExpression, bool aBoolOp) +{ + if (!aOrigExpr->m_term && !aOrigExpr->m_leftChild && !aOrigExpr->m_rightChild) + { + // just use the original expression tree... + // delete the original since we have a new original to use + delete aOrigExpr; + return aExpression; + } + + nsMsgSearchBoolExpression * newExpr = new nsMsgSearchBoolExpression (aOrigExpr, aExpression, aBoolOp); + return (newExpr) ? newExpr : aOrigExpr; +} + +nsMsgSearchBoolExpression::nsMsgSearchBoolExpression() +{ + m_term = nullptr; + m_boolOp = nsMsgSearchBooleanOp::BooleanAND; + m_leftChild = nullptr; + m_rightChild = nullptr; +} + +nsMsgSearchBoolExpression::nsMsgSearchBoolExpression (nsIMsgSearchTerm * newTerm, char * encodingStr) +// we are creating an expression which contains a single search term (newTerm) +// and the search term's IMAP or NNTP encoding value for online search expressions AND +// a boolean evaluation value which is used for offline search expressions. +{ + m_term = newTerm; + m_encodingStr = encodingStr; + m_boolOp = nsMsgSearchBooleanOp::BooleanAND; + + // this expression does not contain sub expressions + m_leftChild = nullptr; + m_rightChild = nullptr; +} + + +nsMsgSearchBoolExpression::nsMsgSearchBoolExpression (nsMsgSearchBoolExpression * expr1, nsMsgSearchBoolExpression * expr2, nsMsgSearchBooleanOperator boolOp) +// we are creating an expression which contains two sub expressions and a boolean operator used to combine +// them. +{ + m_leftChild = expr1; + m_rightChild = expr2; + m_boolOp = boolOp; + + m_term = nullptr; +} + +nsMsgSearchBoolExpression::~nsMsgSearchBoolExpression() +{ + // we must recursively destroy all sub expressions before we destroy ourself.....We leave search terms alone! + delete m_leftChild; + delete m_rightChild; +} + +nsMsgSearchBoolExpression * +nsMsgSearchBoolExpression::leftToRightAddTerm(nsIMsgSearchTerm * newTerm, char * encodingStr) +{ + // we have a base case where this is the first term being added to the expression: + if (!m_term && !m_leftChild && !m_rightChild) + { + m_term = newTerm; + m_encodingStr = encodingStr; + return this; + } + + nsMsgSearchBoolExpression * tempExpr = new nsMsgSearchBoolExpression (newTerm,encodingStr); + if (tempExpr) // make sure creation succeeded + { + bool booleanAnd; + newTerm->GetBooleanAnd(&booleanAnd); + nsMsgSearchBoolExpression * newExpr = new nsMsgSearchBoolExpression (this, tempExpr, booleanAnd); + if (newExpr) + return newExpr; + else + delete tempExpr; // clean up memory allocation in case of failure + } + return this; // in case we failed to create a new expression, return self +} + + +// returns true or false depending on what the current expression evaluates to. +bool nsMsgSearchBoolExpression::OfflineEvaluate(nsIMsgDBHdr *msgToMatch, const char *defaultCharset, + nsIMsgSearchScopeTerm *scope, nsIMsgDatabase *db, const char *headers, + uint32_t headerSize, bool Filtering) +{ + bool result = true; // always default to false positives + bool isAnd; + + if (m_term) // do we contain just a search term? + { + nsMsgSearchOfflineMail::ProcessSearchTerm(msgToMatch, m_term, + defaultCharset, scope, db, headers, headerSize, Filtering, &result); + return result; + } + + // otherwise we must recursively determine the value of our sub expressions + + isAnd = (m_boolOp == nsMsgSearchBooleanOp::BooleanAND); + + if (m_leftChild) + { + result = m_leftChild->OfflineEvaluate(msgToMatch, defaultCharset, + scope, db, headers, headerSize, Filtering); + if ( (result && !isAnd) || (!result && isAnd)) + return result; + } + + // If we got this far, either there was no leftChild (which is impossible) + // or we got (FALSE and OR) or (TRUE and AND) from the first result. That + // means the outcome depends entirely on the rightChild. + if (m_rightChild) + result = m_rightChild->OfflineEvaluate(msgToMatch, defaultCharset, + scope, db, headers, headerSize, Filtering); + + return result; +} + +// ### Maybe we can get rid of these because of our use of nsString??? +// constants used for online searching with IMAP/NNTP encoded search terms. +// the + 1 is to account for null terminators we add at each stage of assembling the expression... +const int sizeOfORTerm = 6+1; // 6 bytes if we are combining two sub expressions with an OR term +const int sizeOfANDTerm = 1+1; // 1 byte if we are combining two sub expressions with an AND term + +int32_t nsMsgSearchBoolExpression::CalcEncodeStrSize() +// recursively examine each sub expression and calculate a final size for the entire IMAP/NNTP encoding +{ + if (!m_term && (!m_leftChild || !m_rightChild)) // is the expression empty? + return 0; + if (m_term) // are we a leaf node? + return m_encodingStr.Length(); + if (m_boolOp == nsMsgSearchBooleanOp::BooleanOR) + return sizeOfORTerm + m_leftChild->CalcEncodeStrSize() + m_rightChild->CalcEncodeStrSize(); + if (m_boolOp == nsMsgSearchBooleanOp::BooleanAND) + return sizeOfANDTerm + m_leftChild->CalcEncodeStrSize() + m_rightChild->CalcEncodeStrSize(); + return 0; +} + + +void nsMsgSearchBoolExpression::GenerateEncodeStr(nsCString * buffer) +// recurively combine sub expressions to form a single IMAP/NNTP encoded string +{ + if ((!m_term && (!m_leftChild || !m_rightChild))) // is expression empty? + return; + + if (m_term) // are we a leaf expression? + { + *buffer += m_encodingStr; + return; + } + + // add encode strings of each sub expression + if (m_boolOp == nsMsgSearchBooleanOp::BooleanOR) + { + *buffer += " (OR"; + + m_leftChild->GenerateEncodeStr(buffer); // insert left expression into the buffer + m_rightChild->GenerateEncodeStr(buffer); // insert right expression into the buffer + + // HACK ALERT!!! if last returned character in the buffer is now a ' ' then we need to remove it because we don't want + // a ' ' to preceded the closing paren in the OR encoding. + uint32_t lastCharPos = buffer->Length() - 1; + if (buffer->CharAt(lastCharPos) == ' ') + { + buffer->SetLength(lastCharPos); + } + + *buffer += ')'; + } + else if (m_boolOp == nsMsgSearchBooleanOp::BooleanAND) + { + m_leftChild->GenerateEncodeStr(buffer); // insert left expression + m_rightChild->GenerateEncodeStr(buffer); + } + return; +} + + +//----------------------------------------------------------------------------- +//---------------- Adapter class for searching offline folders ---------------- +//----------------------------------------------------------------------------- + + +NS_IMPL_ISUPPORTS_INHERITED(nsMsgSearchOfflineMail, nsMsgSearchAdapter, nsIUrlListener) + +nsMsgSearchOfflineMail::nsMsgSearchOfflineMail (nsIMsgSearchScopeTerm *scope, nsISupportsArray *termList) : nsMsgSearchAdapter (scope, termList) +{ +} + +nsMsgSearchOfflineMail::~nsMsgSearchOfflineMail () +{ + // Database should have been closed when the scope term finished. + CleanUpScope(); + NS_ASSERTION(!m_db, "db not closed"); +} + + +nsresult nsMsgSearchOfflineMail::ValidateTerms () +{ + return nsMsgSearchAdapter::ValidateTerms (); +} + + +nsresult nsMsgSearchOfflineMail::OpenSummaryFile () +{ + nsCOMPtr <nsIMsgDatabase> mailDB ; + + nsresult err = NS_OK; + // do password protection of local cache thing. +#ifdef DOING_FOLDER_CACHE_PASSWORDS + if (m_scope->m_folder && m_scope->m_folder->UserNeedsToAuthenticateForFolder(false) && m_scope->m_folder->GetMaster()->PromptForHostPassword(m_scope->m_frame->GetContext(), m_scope->m_folder) != 0) + { + m_scope->m_frame->StopRunning(); + return SearchError_ScopeDone; + } +#endif + nsCOMPtr <nsIDBFolderInfo> folderInfo; + nsCOMPtr <nsIMsgFolder> scopeFolder; + err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + if (NS_SUCCEEDED(err) && scopeFolder) + { + err = scopeFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(m_db)); + } + else + return err; // not sure why m_folder wouldn't be set. + + if (NS_SUCCEEDED(err)) + return NS_OK; + + if ((err == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) || + (err == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)) + { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(scopeFolder, &err); + if (NS_SUCCEEDED(err) && localFolder) + { + nsCOMPtr<nsIMsgSearchSession> searchSession; + m_scope->GetSearchSession(getter_AddRefs(searchSession)); + if (searchSession) + { + nsCOMPtr <nsIMsgWindow> searchWindow; + + searchSession->GetWindow(getter_AddRefs(searchWindow)); + searchSession->PauseSearch(); + localFolder->ParseFolder(searchWindow, this); + } + } + } + else + { + NS_ASSERTION(false, "unexpected error opening db"); + } + + return err; +} + + +nsresult +nsMsgSearchOfflineMail::MatchTermsForFilter(nsIMsgDBHdr *msgToMatch, + nsISupportsArray *termList, + const char *defaultCharset, + nsIMsgSearchScopeTerm * scope, + nsIMsgDatabase * db, + const char * headers, + uint32_t headerSize, + nsMsgSearchBoolExpression ** aExpressionTree, + bool *pResult) +{ + return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, headers, headerSize, true, aExpressionTree, pResult); +} + +// static method which matches a header against a list of search terms. +nsresult +nsMsgSearchOfflineMail::MatchTermsForSearch(nsIMsgDBHdr *msgToMatch, + nsISupportsArray* termList, + const char *defaultCharset, + nsIMsgSearchScopeTerm *scope, + nsIMsgDatabase *db, + nsMsgSearchBoolExpression ** aExpressionTree, + bool *pResult) +{ + + return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, nullptr, 0, false, aExpressionTree, pResult); +} + +nsresult nsMsgSearchOfflineMail::ConstructExpressionTree(nsISupportsArray * termList, + uint32_t termCount, + uint32_t &aStartPosInList, + nsMsgSearchBoolExpression ** aExpressionTree) +{ + nsMsgSearchBoolExpression * finalExpression = *aExpressionTree; + + if (!finalExpression) + finalExpression = new nsMsgSearchBoolExpression(); + + while (aStartPosInList < termCount) + { + nsCOMPtr<nsIMsgSearchTerm> pTerm; + termList->QueryElementAt(aStartPosInList, NS_GET_IID(nsIMsgSearchTerm), (void **)getter_AddRefs(pTerm)); + NS_ASSERTION (pTerm, "couldn't get term to match"); + + bool beginsGrouping; + bool endsGrouping; + pTerm->GetBeginsGrouping(&beginsGrouping); + pTerm->GetEndsGrouping(&endsGrouping); + + if (beginsGrouping) + { + //temporarily turn off the grouping for our recursive call + pTerm->SetBeginsGrouping(false); + nsMsgSearchBoolExpression * innerExpression = new nsMsgSearchBoolExpression(); + + // the first search term in the grouping is the one that holds the operator for how this search term + // should be joined with the expressions to it's left. + bool booleanAnd; + pTerm->GetBooleanAnd(&booleanAnd); + + // now add this expression tree to our overall expression tree... + finalExpression = nsMsgSearchBoolExpression::AddExpressionTree(finalExpression, innerExpression, booleanAnd); + + // recursively process this inner expression + ConstructExpressionTree(termList, termCount, aStartPosInList, + &finalExpression->m_rightChild); + + // undo our damage + pTerm->SetBeginsGrouping(true); + + } + else + { + finalExpression = nsMsgSearchBoolExpression::AddSearchTerm(finalExpression, pTerm, nullptr); // add the term to the expression tree + + if (endsGrouping) + break; + } + + aStartPosInList++; + } // while we still have terms to process in this group + + *aExpressionTree = finalExpression; + + return NS_OK; +} + +nsresult nsMsgSearchOfflineMail::ProcessSearchTerm(nsIMsgDBHdr *msgToMatch, + nsIMsgSearchTerm * aTerm, + const char *defaultCharset, + nsIMsgSearchScopeTerm * scope, + nsIMsgDatabase * db, + const char * headers, + uint32_t headerSize, + bool Filtering, + bool *pResult) +{ + nsresult err = NS_OK; + nsCString recipients; + nsCString ccList; + nsCString matchString; + nsCString msgCharset; + const char *charset; + bool charsetOverride = false; /* XXX BUG 68706 */ + uint32_t msgFlags; + bool result; + bool matchAll; + + NS_ENSURE_ARG_POINTER(pResult); + + aTerm->GetMatchAll(&matchAll); + if (matchAll) + { + *pResult = true; + return NS_OK; + } + *pResult = false; + + nsMsgSearchAttribValue attrib; + aTerm->GetAttrib(&attrib); + msgToMatch->GetCharset(getter_Copies(msgCharset)); + charset = msgCharset.get(); + if (!charset || !*charset) + charset = (const char*)defaultCharset; + msgToMatch->GetFlags(&msgFlags); + + switch (attrib) + { + case nsMsgSearchAttrib::Sender: + msgToMatch->GetAuthor(getter_Copies(matchString)); + err = aTerm->MatchRfc822String(matchString, charset, &result); + break; + case nsMsgSearchAttrib::Subject: + { + msgToMatch->GetSubject(getter_Copies(matchString) /* , true */); + if (msgFlags & nsMsgMessageFlags::HasRe) + { + // Make sure we pass along the "Re: " part of the subject if this is a reply. + nsCString reString; + reString.Assign("Re: "); + reString.Append(matchString); + err = aTerm->MatchRfc2047String(reString, charset, charsetOverride, &result); + } + else + err = aTerm->MatchRfc2047String(matchString, charset, charsetOverride, &result); + break; + } + case nsMsgSearchAttrib::ToOrCC: + { + bool boolKeepGoing; + aTerm->GetMatchAllBeforeDeciding(&boolKeepGoing); + msgToMatch->GetRecipients(getter_Copies(recipients)); + err = aTerm->MatchRfc822String(recipients, charset, &result); + if (boolKeepGoing == result) + { + msgToMatch->GetCcList(getter_Copies(ccList)); + err = aTerm->MatchRfc822String(ccList, charset, &result); + } + break; + } + case nsMsgSearchAttrib::AllAddresses: + { + bool boolKeepGoing; + aTerm->GetMatchAllBeforeDeciding(&boolKeepGoing); + msgToMatch->GetRecipients(getter_Copies(recipients)); + err = aTerm->MatchRfc822String(recipients, charset, &result); + if (boolKeepGoing == result) + { + msgToMatch->GetCcList(getter_Copies(ccList)); + err = aTerm->MatchRfc822String(ccList, charset, &result); + } + if (boolKeepGoing == result) + { + msgToMatch->GetAuthor(getter_Copies(matchString)); + err = aTerm->MatchRfc822String(matchString, charset, &result); + } + if (boolKeepGoing == result) + { + nsCString bccList; + msgToMatch->GetBccList(getter_Copies(bccList)); + err = aTerm->MatchRfc822String(bccList, charset, &result); + } + break; + } + case nsMsgSearchAttrib::Body: + { + uint64_t messageOffset; + uint32_t lineCount; + msgToMatch->GetMessageOffset(&messageOffset); + msgToMatch->GetLineCount(&lineCount); + err = aTerm->MatchBody (scope, messageOffset, lineCount, charset, msgToMatch, db, &result); + break; + } + case nsMsgSearchAttrib::Date: + { + PRTime date; + msgToMatch->GetDate(&date); + err = aTerm->MatchDate (date, &result); + + break; + } + case nsMsgSearchAttrib::HasAttachmentStatus: + case nsMsgSearchAttrib::MsgStatus: + err = aTerm->MatchStatus (msgFlags, &result); + break; + case nsMsgSearchAttrib::Priority: + { + nsMsgPriorityValue msgPriority; + msgToMatch->GetPriority(&msgPriority); + err = aTerm->MatchPriority (msgPriority, &result); + break; + } + case nsMsgSearchAttrib::Size: + { + uint32_t messageSize; + msgToMatch->GetMessageSize(&messageSize); + err = aTerm->MatchSize (messageSize, &result); + break; + } + case nsMsgSearchAttrib::To: + msgToMatch->GetRecipients(getter_Copies(recipients)); + err = aTerm->MatchRfc822String(recipients, charset, &result); + break; + case nsMsgSearchAttrib::CC: + msgToMatch->GetCcList(getter_Copies(ccList)); + err = aTerm->MatchRfc822String(ccList, charset, &result); + break; + case nsMsgSearchAttrib::AgeInDays: + { + PRTime date; + msgToMatch->GetDate(&date); + err = aTerm->MatchAge (date, &result); + break; + } + case nsMsgSearchAttrib::Label: + { + nsMsgLabelValue label; + msgToMatch->GetLabel(&label); + err = aTerm->MatchLabel(label, &result); + break; + } + case nsMsgSearchAttrib::Keywords: + { + nsCString keywords; + nsMsgLabelValue label; + msgToMatch->GetStringProperty("keywords", getter_Copies(keywords)); + msgToMatch->GetLabel(&label); + if (label >= 1) + { + if (!keywords.IsEmpty()) + keywords.Append(' '); + keywords.Append("$label"); + keywords.Append(label + '0'); + } + err = aTerm->MatchKeyword(keywords, &result); + break; + } + case nsMsgSearchAttrib::JunkStatus: + { + nsCString junkScoreStr; + msgToMatch->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + err = aTerm->MatchJunkStatus(junkScoreStr.get(), &result); + break; + } + case nsMsgSearchAttrib::JunkPercent: + { + // When the junk status is set by the plugin, use junkpercent (if available) + // Otherwise, use the limits (0 or 100) depending on the junkscore. + uint32_t junkPercent; + nsresult rv; + nsCString junkScoreOriginStr; + nsCString junkPercentStr; + msgToMatch->GetStringProperty("junkscoreorigin", getter_Copies(junkScoreOriginStr)); + msgToMatch->GetStringProperty("junkpercent", getter_Copies(junkPercentStr)); + if ( junkScoreOriginStr.EqualsLiteral("plugin") && + !junkPercentStr.IsEmpty()) + { + junkPercent = junkPercentStr.ToInteger(&rv); + NS_ENSURE_SUCCESS(rv, rv); + } + else + { + nsCString junkScoreStr; + msgToMatch->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + // When junk status is not set (uncertain) we'll set the value to ham. + if (junkScoreStr.IsEmpty()) + junkPercent = nsIJunkMailPlugin::IS_HAM_SCORE; + else + { + junkPercent = junkScoreStr.ToInteger(&rv); + NS_ENSURE_SUCCESS(rv, rv); + } + } + err = aTerm->MatchJunkPercent(junkPercent, &result); + break; + } + case nsMsgSearchAttrib::JunkScoreOrigin: + { + nsCString junkScoreOriginStr; + msgToMatch->GetStringProperty("junkscoreorigin", getter_Copies(junkScoreOriginStr)); + err = aTerm->MatchJunkScoreOrigin(junkScoreOriginStr.get(), &result); + break; + } + case nsMsgSearchAttrib::HdrProperty: + { + err = aTerm->MatchHdrProperty(msgToMatch, &result); + break; + } + case nsMsgSearchAttrib::Uint32HdrProperty: + { + err = aTerm->MatchUint32HdrProperty(msgToMatch, &result); + break; + } + case nsMsgSearchAttrib::Custom: + { + err = aTerm->MatchCustom(msgToMatch, &result); + break; + } + case nsMsgSearchAttrib::FolderFlag: + { + err = aTerm->MatchFolderFlag(msgToMatch, &result); + break; + } + default: + // XXX todo + // for the temporary return receipts filters, we use a custom header for Content-Type + // but unlike the other custom headers, this one doesn't show up in the search / filter + // UI. we set the attrib to be nsMsgSearchAttrib::OtherHeader, where as for user + // defined custom headers start at nsMsgSearchAttrib::OtherHeader + 1 + // Not sure if there is a better way to do this yet. Maybe reserve the last + // custom header for ::Content-Type? But if we do, make sure that change + // doesn't cause nsMsgFilter::GetTerm() to change, and start making us + // ask IMAP servers for the Content-Type header on all messages. + if (attrib >= nsMsgSearchAttrib::OtherHeader && + attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) + { + uint32_t lineCount; + msgToMatch->GetLineCount(&lineCount); + uint64_t messageOffset; + msgToMatch->GetMessageOffset(&messageOffset); + err = aTerm->MatchArbitraryHeader(scope, lineCount,charset, + charsetOverride, msgToMatch, db, + headers, headerSize, Filtering, + &result); + } + else { + err = NS_ERROR_INVALID_ARG; // ### was SearchError_InvalidAttribute + result = false; + } + } + + *pResult = result; + return err; +} + +nsresult nsMsgSearchOfflineMail::MatchTerms(nsIMsgDBHdr *msgToMatch, + nsISupportsArray * termList, + const char *defaultCharset, + nsIMsgSearchScopeTerm * scope, + nsIMsgDatabase * db, + const char * headers, + uint32_t headerSize, + bool Filtering, + nsMsgSearchBoolExpression ** aExpressionTree, + bool *pResult) +{ + NS_ENSURE_ARG(aExpressionTree); + nsresult err; + + if (!*aExpressionTree) + { + uint32_t initialPos = 0; + uint32_t count; + termList->Count(&count); + err = ConstructExpressionTree(termList, count, initialPos, aExpressionTree); + if (NS_FAILED(err)) + return err; + } + + // evaluate the expression tree and return the result + *pResult = (*aExpressionTree) + ? (*aExpressionTree)->OfflineEvaluate(msgToMatch, + defaultCharset, scope, db, headers, headerSize, Filtering) + :true; // vacuously true... + + return NS_OK; +} + +nsresult nsMsgSearchOfflineMail::Search(bool *aDone) +{ + nsresult err = NS_OK; + + NS_ENSURE_ARG(aDone); + nsresult dbErr = NS_OK; + nsCOMPtr<nsIMsgDBHdr> msgDBHdr; + nsMsgSearchBoolExpression *expressionTree = nullptr; + + const uint32_t kTimeSliceInMS = 200; + + *aDone = false; + // Try to open the DB lazily. This will set up a parser if one is required + if (!m_db) + err = OpenSummaryFile (); + if (!m_db) // must be reparsing. + return err; + + // Reparsing is unnecessary or completed + if (NS_SUCCEEDED(err)) + { + if (!m_listContext) + dbErr = m_db->ReverseEnumerateMessages(getter_AddRefs(m_listContext)); + if (NS_SUCCEEDED(dbErr) && m_listContext) + { + PRIntervalTime startTime = PR_IntervalNow(); + while (!*aDone) // we'll break out of the loop after kTimeSliceInMS milliseconds + { + nsCOMPtr<nsISupports> currentItem; + + dbErr = m_listContext->GetNext(getter_AddRefs(currentItem)); + if(NS_SUCCEEDED(dbErr)) + { + msgDBHdr = do_QueryInterface(currentItem, &dbErr); + } + if (NS_FAILED(dbErr)) + *aDone = true; //###phil dbErr is dropped on the floor. just note that we did have an error so we'll clean up later + else + { + bool match = false; + nsAutoString nullCharset, folderCharset; + GetSearchCharsets(nullCharset, folderCharset); + NS_ConvertUTF16toUTF8 charset(folderCharset); + // Is this message a hit? + err = MatchTermsForSearch (msgDBHdr, m_searchTerms, charset.get(), m_scope, m_db, &expressionTree, &match); + // Add search hits to the results list + if (NS_SUCCEEDED(err) && match) + { + AddResultElement (msgDBHdr); + } + PRIntervalTime elapsedTime = PR_IntervalNow() - startTime; + // check if more than kTimeSliceInMS milliseconds have elapsed in this time slice started + if (PR_IntervalToMilliseconds(elapsedTime) > kTimeSliceInMS) + break; + } + } + } + } + else + *aDone = true; // we couldn't open up the DB. This is an unrecoverable error so mark the scope as done. + + delete expressionTree; + + // in the past an error here would cause an "infinite" search because the url would continue to run... + // i.e. if we couldn't open the database, it returns an error code but the caller of this function says, oh, + // we did not finish so continue...what we really want is to treat this current scope as done + if (*aDone) + CleanUpScope(); // Do clean up for end-of-scope processing + return err; +} + +void nsMsgSearchOfflineMail::CleanUpScope() +{ + // Let go of the DB when we're done with it so we don't kill the db cache + if (m_db) + { + m_listContext = nullptr; + m_db->Close(false); + } + m_db = nullptr; + + if (m_scope) + m_scope->CloseInputStream(); +} + +NS_IMETHODIMP nsMsgSearchOfflineMail::AddResultElement (nsIMsgDBHdr *pHeaders) +{ + nsresult err = NS_OK; + + nsCOMPtr<nsIMsgSearchSession> searchSession; + m_scope->GetSearchSession(getter_AddRefs(searchSession)); + if (searchSession) + { + nsCOMPtr <nsIMsgFolder> scopeFolder; + err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + searchSession->AddSearchHit(pHeaders, scopeFolder); + } + return err; +} + +NS_IMETHODIMP +nsMsgSearchOfflineMail::Abort () +{ + // Let go of the DB when we're done with it so we don't kill the db cache + if (m_db) + m_db->Close(true /* commit in case we downloaded new headers */); + m_db = nullptr; + return nsMsgSearchAdapter::Abort (); +} + +/* void OnStartRunningUrl (in nsIURI url); */ +NS_IMETHODIMP nsMsgSearchOfflineMail::OnStartRunningUrl(nsIURI *url) +{ + return NS_OK; +} + +/* void OnStopRunningUrl (in nsIURI url, in nsresult aExitCode); */ +NS_IMETHODIMP nsMsgSearchOfflineMail::OnStopRunningUrl(nsIURI *url, nsresult aExitCode) +{ + nsCOMPtr<nsIMsgSearchSession> searchSession; + if (m_scope) + m_scope->GetSearchSession(getter_AddRefs(searchSession)); + if (searchSession) + searchSession->ResumeSearch(); + + return NS_OK; +} + +nsMsgSearchOfflineNews::nsMsgSearchOfflineNews (nsIMsgSearchScopeTerm *scope, nsISupportsArray *termList) : nsMsgSearchOfflineMail (scope, termList) +{ +} + + +nsMsgSearchOfflineNews::~nsMsgSearchOfflineNews () +{ +} + + +nsresult nsMsgSearchOfflineNews::OpenSummaryFile () +{ + nsresult err = NS_OK; + nsCOMPtr <nsIDBFolderInfo> folderInfo; + nsCOMPtr <nsIMsgFolder> scopeFolder; + err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + // code here used to check if offline store existed, which breaks offline news. + if (NS_SUCCEEDED(err) && scopeFolder) + err = scopeFolder->GetMsgDatabase(getter_AddRefs(m_db)); + return err; +} + +nsresult nsMsgSearchOfflineNews::ValidateTerms () +{ + return nsMsgSearchOfflineMail::ValidateTerms (); +} + +// local helper functions to set subsets of the validity table + +nsresult SetJunk(nsIMsgSearchValidityTable* aTable) +{ + NS_ENSURE_ARG_POINTER(aTable); + + aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + aTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + aTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + aTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + aTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + aTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + aTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + aTable->SetEnabled(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + + return NS_OK; +} + +nsresult SetBody(nsIMsgSearchValidityTable* aTable) +{ + NS_ENSURE_ARG_POINTER(aTable); + aTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + + return NS_OK; +} + +// set the base validity table values for local news +nsresult SetLocalNews(nsIMsgSearchValidityTable* aTable) +{ + NS_ENSURE_ARG_POINTER(aTable); + + aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + aTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + aTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + aTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + aTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + aTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + aTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + aTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + aTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + + aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + return NS_OK; + +} + +nsresult nsMsgSearchValidityManager::InitLocalNewsTable() +{ + NS_ASSERTION (nullptr == m_localNewsTable, "already have local news validity table"); + nsresult rv = NewTable(getter_AddRefs(m_localNewsTable)); + NS_ENSURE_SUCCESS(rv, rv); + return SetLocalNews(m_localNewsTable); +} + +nsresult nsMsgSearchValidityManager::InitLocalNewsBodyTable() +{ + NS_ASSERTION (nullptr == m_localNewsBodyTable, "already have local news+body validity table"); + nsresult rv = NewTable(getter_AddRefs(m_localNewsBodyTable)); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetLocalNews(m_localNewsBodyTable); + NS_ENSURE_SUCCESS(rv, rv); + return SetBody(m_localNewsBodyTable); +} + +nsresult nsMsgSearchValidityManager::InitLocalNewsJunkTable() +{ + NS_ASSERTION (nullptr == m_localNewsJunkTable, "already have local news+junk validity table"); + nsresult rv = NewTable(getter_AddRefs(m_localNewsJunkTable)); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetLocalNews(m_localNewsJunkTable); + NS_ENSURE_SUCCESS(rv, rv); + return SetJunk(m_localNewsJunkTable); +} + +nsresult nsMsgSearchValidityManager::InitLocalNewsJunkBodyTable() +{ + NS_ASSERTION (nullptr == m_localNewsJunkBodyTable, "already have local news+junk+body validity table"); + nsresult rv = NewTable(getter_AddRefs(m_localNewsJunkBodyTable)); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetLocalNews(m_localNewsJunkBodyTable); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetJunk(m_localNewsJunkBodyTable); + NS_ENSURE_SUCCESS(rv, rv); + return SetBody(m_localNewsJunkBodyTable); +} diff --git a/mailnews/base/search/src/nsMsgLocalSearch.h b/mailnews/base/search/src/nsMsgLocalSearch.h new file mode 100644 index 000000000..020d7b579 --- /dev/null +++ b/mailnews/base/search/src/nsMsgLocalSearch.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgLocalSearch_H +#define _nsMsgLocalSearch_H + +// inherit interface here +#include "mozilla/Attributes.h" +#include "nsIMsgSearchAdapter.h" +#include "nsIUrlListener.h" + +// inherit base implementation +#include "nsMsgSearchAdapter.h" +#include "nsISimpleEnumerator.h" + + +class nsIMsgDBHdr; +class nsIMsgSearchScopeTerm; +class nsIMsgFolder; +class nsMsgSearchBoolExpression; + +class nsMsgSearchOfflineMail : public nsMsgSearchAdapter, public nsIUrlListener +{ +public: + nsMsgSearchOfflineMail (nsIMsgSearchScopeTerm*, nsISupportsArray *); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIURLLISTENER + + NS_IMETHOD ValidateTerms () override; + NS_IMETHOD Search (bool *aDone) override; + NS_IMETHOD Abort () override; + NS_IMETHOD AddResultElement (nsIMsgDBHdr *) override; + + static nsresult MatchTermsForFilter(nsIMsgDBHdr * msgToMatch, + nsISupportsArray *termList, + const char *defaultCharset, + nsIMsgSearchScopeTerm *scope, + nsIMsgDatabase * db, + const char * headers, + uint32_t headerSize, + nsMsgSearchBoolExpression ** aExpressionTree, + bool *pResult); + + static nsresult MatchTermsForSearch(nsIMsgDBHdr * msgTomatch, + nsISupportsArray * termList, + const char *defaultCharset, + nsIMsgSearchScopeTerm *scope, + nsIMsgDatabase *db, + nsMsgSearchBoolExpression ** aExpressionTree, + bool *pResult); + + virtual nsresult OpenSummaryFile (); + + static nsresult ProcessSearchTerm(nsIMsgDBHdr *msgToMatch, + nsIMsgSearchTerm * aTerm, + const char *defaultCharset, + nsIMsgSearchScopeTerm * scope, + nsIMsgDatabase * db, + const char * headers, + uint32_t headerSize, + bool Filtering, + bool *pResult); +protected: + virtual ~nsMsgSearchOfflineMail(); + static nsresult MatchTerms(nsIMsgDBHdr *msgToMatch, + nsISupportsArray *termList, + const char *defaultCharset, + nsIMsgSearchScopeTerm *scope, + nsIMsgDatabase * db, + const char * headers, + uint32_t headerSize, + bool ForFilters, + nsMsgSearchBoolExpression ** aExpressionTree, + bool *pResult); + + static nsresult ConstructExpressionTree(nsISupportsArray * termList, + uint32_t termCount, + uint32_t &aStartPosInList, + nsMsgSearchBoolExpression ** aExpressionTree); + + nsCOMPtr <nsIMsgDatabase> m_db; + nsCOMPtr<nsISimpleEnumerator> m_listContext; + void CleanUpScope(); +}; + + +class nsMsgSearchOfflineNews : public nsMsgSearchOfflineMail +{ +public: + nsMsgSearchOfflineNews (nsIMsgSearchScopeTerm*, nsISupportsArray *); + virtual ~nsMsgSearchOfflineNews (); + NS_IMETHOD ValidateTerms () override; + + virtual nsresult OpenSummaryFile () override; +}; + + + +#endif + diff --git a/mailnews/base/search/src/nsMsgSearchAdapter.cpp b/mailnews/base/search/src/nsMsgSearchAdapter.cpp new file mode 100644 index 000000000..a6f877830 --- /dev/null +++ b/mailnews/base/search/src/nsMsgSearchAdapter.cpp @@ -0,0 +1,1332 @@ +/* -*- 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/. */ + +#include "msgCore.h" +#include "nsTextFormatter.h" +#include "nsMsgSearchCore.h" +#include "nsMsgSearchAdapter.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsMsgI18N.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefLocalizedString.h" +#include "nsMsgSearchTerm.h" +#include "nsMsgSearchBoolExpression.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "prprf.h" +#include "mozilla/UniquePtr.h" +#include "prmem.h" +#include "MailNewsTypes.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "nsMsgMessageFlags.h" +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (disable : 4996) +#endif +#include "nsISupportsArray.h" +#include "nsAlgorithm.h" +#include <algorithm> +#include "mozilla/Attributes.h" + +// This stuff lives in the base class because the IMAP search syntax +// is used by the Dredd SEARCH command as well as IMAP itself + +// km - the NOT and HEADER strings are not encoded with a trailing +// <space> because they always precede a mnemonic that has a +// preceding <space> and double <space> characters cause some +// imap servers to return an error. +const char *nsMsgSearchAdapter::m_kImapBefore = " SENTBEFORE "; +const char *nsMsgSearchAdapter::m_kImapBody = " BODY "; +const char *nsMsgSearchAdapter::m_kImapCC = " CC "; +const char *nsMsgSearchAdapter::m_kImapFrom = " FROM "; +const char *nsMsgSearchAdapter::m_kImapNot = " NOT"; +const char *nsMsgSearchAdapter::m_kImapUnDeleted= " UNDELETED"; +const char *nsMsgSearchAdapter::m_kImapOr = " OR"; +const char *nsMsgSearchAdapter::m_kImapSince = " SENTSINCE "; +const char *nsMsgSearchAdapter::m_kImapSubject = " SUBJECT "; +const char *nsMsgSearchAdapter::m_kImapTo = " TO "; +const char *nsMsgSearchAdapter::m_kImapHeader = " HEADER"; +const char *nsMsgSearchAdapter::m_kImapAnyText = " TEXT "; +const char *nsMsgSearchAdapter::m_kImapKeyword = " KEYWORD "; +const char *nsMsgSearchAdapter::m_kNntpKeywords = " KEYWORDS "; //ggrrrr... +const char *nsMsgSearchAdapter::m_kImapSentOn = " SENTON "; +const char *nsMsgSearchAdapter::m_kImapSeen = " SEEN "; +const char *nsMsgSearchAdapter::m_kImapAnswered = " ANSWERED "; +const char *nsMsgSearchAdapter::m_kImapNotSeen = " UNSEEN "; +const char *nsMsgSearchAdapter::m_kImapNotAnswered = " UNANSWERED "; +const char *nsMsgSearchAdapter::m_kImapCharset = " CHARSET "; +const char *nsMsgSearchAdapter::m_kImapSizeSmaller = " SMALLER "; +const char *nsMsgSearchAdapter::m_kImapSizeLarger = " LARGER "; +const char *nsMsgSearchAdapter::m_kImapNew = " NEW "; +const char *nsMsgSearchAdapter::m_kImapNotNew = " OLD SEEN "; +const char *nsMsgSearchAdapter::m_kImapFlagged = " FLAGGED "; +const char *nsMsgSearchAdapter::m_kImapNotFlagged = " UNFLAGGED "; + +#define PREF_CUSTOM_HEADERS "mailnews.customHeaders" + +NS_IMETHODIMP nsMsgSearchAdapter::FindTargetFolder(const nsMsgResultElement *,nsIMsgFolder * *) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchAdapter::ModifyResultElement(nsMsgResultElement *, nsMsgSearchValue *) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchAdapter::OpenResultElement(nsMsgResultElement *) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMPL_ISUPPORTS(nsMsgSearchAdapter, nsIMsgSearchAdapter) + +nsMsgSearchAdapter::nsMsgSearchAdapter(nsIMsgSearchScopeTerm *scope, nsISupportsArray *searchTerms) + : m_searchTerms(searchTerms) +{ + m_scope = scope; +} + +nsMsgSearchAdapter::~nsMsgSearchAdapter() +{ +} + +NS_IMETHODIMP nsMsgSearchAdapter::ClearScope() +{ + if (m_scope) + { + m_scope->CloseInputStream(); + m_scope = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchAdapter::ValidateTerms () +{ + // all this used to do is check if the object had been deleted - we can skip that. + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchAdapter::Abort () +{ + return NS_ERROR_NOT_IMPLEMENTED; + +} +NS_IMETHODIMP nsMsgSearchAdapter::Search (bool *aDone) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchAdapter::SendUrl () +{ + return NS_OK; +} + +/* void CurrentUrlDone (in nsresult exitCode); */ +NS_IMETHODIMP nsMsgSearchAdapter::CurrentUrlDone(nsresult exitCode) +{ + // base implementation doesn't need to do anything. + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchAdapter::GetEncoding (char **encoding) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchAdapter::AddResultElement (nsIMsgDBHdr *pHeaders) +{ + NS_ASSERTION(false, "shouldn't call this base class impl"); + return NS_ERROR_FAILURE; +} + + +NS_IMETHODIMP nsMsgSearchAdapter::AddHit(nsMsgKey key) +{ + NS_ASSERTION(false, "shouldn't call this base class impl"); + return NS_ERROR_FAILURE; +} + + +char * +nsMsgSearchAdapter::GetImapCharsetParam(const char16_t *destCharset) +{ + char *result = nullptr; + + // Specify a character set unless we happen to be US-ASCII. + if (NS_strcmp(destCharset, u"us-ascii")) + result = PR_smprintf("%s%s", nsMsgSearchAdapter::m_kImapCharset, NS_ConvertUTF16toUTF8(destCharset).get()); + + return result; +} + +/* + 09/21/2000 - taka@netscape.com + This method is bogus. Escape must be done against char * not char16_t * + should be rewritten later. + for now, just duplicate the string. +*/ +char16_t *nsMsgSearchAdapter::EscapeSearchUrl (const char16_t *nntpCommand) +{ + return nntpCommand ? NS_strdup(nntpCommand) : nullptr; +} + +/* + 09/21/2000 - taka@netscape.com + This method is bogus. Escape must be done against char * not char16_t * + should be rewritten later. + for now, just duplicate the string. +*/ +char16_t * +nsMsgSearchAdapter::EscapeImapSearchProtocol(const char16_t *imapCommand) +{ + return imapCommand ? NS_strdup(imapCommand) : nullptr; +} + +/* + 09/21/2000 - taka@netscape.com + This method is bogus. Escape must be done against char * not char16_t * + should be rewritten later. + for now, just duplicate the string. +*/ +char16_t * +nsMsgSearchAdapter::EscapeQuoteImapSearchProtocol(const char16_t *imapCommand) +{ + return imapCommand ? NS_strdup(imapCommand) : nullptr; +} + +char *nsMsgSearchAdapter::UnEscapeSearchUrl (const char *commandSpecificData) +{ + char *result = (char*) PR_Malloc (strlen(commandSpecificData) + 1); + if (result) + { + char *resultPtr = result; + while (1) + { + char ch = *commandSpecificData++; + if (!ch) + break; + if (ch == '\\') + { + char scratchBuf[3]; + scratchBuf[0] = (char) *commandSpecificData++; + scratchBuf[1] = (char) *commandSpecificData++; + scratchBuf[2] = '\0'; + unsigned int accum = 0; + sscanf (scratchBuf, "%X", &accum); + *resultPtr++ = (char) accum; + } + else + *resultPtr++ = ch; + } + *resultPtr = '\0'; + } + return result; +} + + +nsresult +nsMsgSearchAdapter::GetSearchCharsets(nsAString &srcCharset, nsAString &dstCharset) +{ + nsresult rv; + + if (m_defaultCharset.IsEmpty()) + { + m_forceAsciiSearch = false; // set the default value in case of error + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIPrefLocalizedString> localizedstr; + rv = prefs->GetComplexValue("mailnews.view_default_charset", NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(localizedstr)); + if (NS_SUCCEEDED(rv)) + localizedstr->GetData(getter_Copies(m_defaultCharset)); + + prefs->GetBoolPref("mailnews.force_ascii_search", &m_forceAsciiSearch); + } + } + srcCharset = m_defaultCharset.IsEmpty() ? + static_cast<const nsAString&>(NS_LITERAL_STRING("ISO-8859-1")) : + m_defaultCharset; + + if (m_scope) + { + // ### DMB is there a way to get the charset for the "window"? + + nsCOMPtr<nsIMsgFolder> folder; + rv = m_scope->GetFolder(getter_AddRefs(folder)); + + // Ask the newsgroup/folder for its csid. + if (NS_SUCCEEDED(rv) && folder) + { + nsCString folderCharset; + folder->GetCharset(folderCharset); + dstCharset.Append(NS_ConvertASCIItoUTF16(folderCharset)); + } + } + else + dstCharset.Assign(srcCharset); + + // If + // the destination is still CS_DEFAULT, make the destination match + // the source. (CS_DEFAULT is an indication that the charset + // was undefined or unavailable.) + // ### well, it's not really anymore. Is there an equivalent? + if (dstCharset.Equals(m_defaultCharset)) + { + dstCharset.Assign(srcCharset); + } + + if (m_forceAsciiSearch) + { + // Special cases to use in order to force US-ASCII searching with Latin1 + // or MacRoman text. Eurgh. This only has to happen because IMAP + // and Dredd servers currently (4/23/97) only support US-ASCII. + // + // If the dest csid is ISO Latin 1 or MacRoman, attempt to convert the + // source text to US-ASCII. (Not for now.) + // if ((dst_csid == CS_LATIN1) || (dst_csid == CS_MAC_ROMAN)) + dstCharset.AssignLiteral("us-ascii"); + } + + return NS_OK; +} + +nsresult nsMsgSearchAdapter::EncodeImapTerm (nsIMsgSearchTerm *term, bool reallyDredd, const char16_t *srcCharset, const char16_t *destCharset, char **ppOutTerm) +{ + NS_ENSURE_ARG_POINTER(term); + NS_ENSURE_ARG_POINTER(ppOutTerm); + + nsresult err = NS_OK; + bool useNot = false; + bool useQuotes = false; + bool ignoreValue = false; + nsAutoCString arbitraryHeader; + const char *whichMnemonic = nullptr; + const char *orHeaderMnemonic = nullptr; + + *ppOutTerm = nullptr; + + nsCOMPtr <nsIMsgSearchValue> searchValue; + nsresult rv = term->GetValue(getter_AddRefs(searchValue)); + + NS_ENSURE_SUCCESS(rv,rv); + + nsMsgSearchOpValue op; + term->GetOp(&op); + + if (op == nsMsgSearchOp::DoesntContain || op == nsMsgSearchOp::Isnt) + useNot = true; + + nsMsgSearchAttribValue attrib; + term->GetAttrib(&attrib); + + switch (attrib) + { + case nsMsgSearchAttrib::ToOrCC: + orHeaderMnemonic = m_kImapCC; + // fall through to case nsMsgSearchAttrib::To: + MOZ_FALLTHROUGH; + case nsMsgSearchAttrib::To: + whichMnemonic = m_kImapTo; + break; + case nsMsgSearchAttrib::CC: + whichMnemonic = m_kImapCC; + break; + case nsMsgSearchAttrib::Sender: + whichMnemonic = m_kImapFrom; + break; + case nsMsgSearchAttrib::Subject: + whichMnemonic = m_kImapSubject; + break; + case nsMsgSearchAttrib::Body: + whichMnemonic = m_kImapBody; + break; + case nsMsgSearchAttrib::AgeInDays: // added for searching online for age in days... + // for AgeInDays, we are actually going to perform a search by date, so convert the operations for age + // to the IMAP mnemonics that we would use for date! + { + // If we have a future date, the > and < are reversed. + // e.g. ageInDays > 2 means more than 2 days old ("date before X") whereas + // ageInDays > -2 should be more than 2 days in the future ("date after X") + int32_t ageInDays; + searchValue->GetAge(&ageInDays); + bool dateInFuture = (ageInDays < 0); + switch (op) + { + case nsMsgSearchOp::IsGreaterThan: + whichMnemonic = (!dateInFuture) ? m_kImapBefore : m_kImapSince; + break; + case nsMsgSearchOp::IsLessThan: + whichMnemonic = (!dateInFuture) ? m_kImapSince : m_kImapBefore; + break; + case nsMsgSearchOp::Is: + whichMnemonic = m_kImapSentOn; + break; + default: + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + } + break; + case nsMsgSearchAttrib::Size: + switch (op) + { + case nsMsgSearchOp::IsGreaterThan: + whichMnemonic = m_kImapSizeLarger; + break; + case nsMsgSearchOp::IsLessThan: + whichMnemonic = m_kImapSizeSmaller; + break; + default: + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + break; + case nsMsgSearchAttrib::Date: + switch (op) + { + case nsMsgSearchOp::IsBefore: + whichMnemonic = m_kImapBefore; + break; + case nsMsgSearchOp::IsAfter: + whichMnemonic = m_kImapSince; + break; + case nsMsgSearchOp::Isnt: /* we've already added the "Not" so just process it like it was a date is search */ + case nsMsgSearchOp::Is: + whichMnemonic = m_kImapSentOn; + break; + default: + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + break; + case nsMsgSearchAttrib::AnyText: + whichMnemonic = m_kImapAnyText; + break; + case nsMsgSearchAttrib::Keywords: + whichMnemonic = m_kImapKeyword; + break; + case nsMsgSearchAttrib::MsgStatus: + useNot = false; // bizarrely, NOT SEEN is wrong, but UNSEEN is right. + ignoreValue = true; // the mnemonic is all we need + uint32_t status; + searchValue->GetStatus(&status); + + switch (status) + { + case nsMsgMessageFlags::Read: + whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapSeen : m_kImapNotSeen; + break; + case nsMsgMessageFlags::Replied: + whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapAnswered : m_kImapNotAnswered; + break; + case nsMsgMessageFlags::New: + whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapNew : m_kImapNotNew; + break; + case nsMsgMessageFlags::Marked: + whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapFlagged : m_kImapNotFlagged; + break; + default: + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + break; + default: + if ( attrib > nsMsgSearchAttrib::OtherHeader && attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) + { + nsCString arbitraryHeaderTerm; + term->GetArbitraryHeader(arbitraryHeaderTerm); + if (!arbitraryHeaderTerm.IsEmpty()) + { + arbitraryHeader.AssignLiteral(" \""); + arbitraryHeader.Append(arbitraryHeaderTerm); + arbitraryHeader.AppendLiteral("\" "); + whichMnemonic = arbitraryHeader.get(); + } + else + return NS_ERROR_FAILURE; + } + else + { + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + } + + char *value = nullptr; + char dateBuf[100]; + dateBuf[0] = '\0'; + + bool valueWasAllocated = false; + if (attrib == nsMsgSearchAttrib::Date) + { + // note that there used to be code here that encoded an RFC822 date for imap searches. + // The IMAP RFC 2060 is misleading to the point that it looks like it requires an RFC822 + // date but really it expects dd-mmm-yyyy, like dredd, and refers to the RFC822 date only in that the + // dd-mmm-yyyy date will match the RFC822 date within the message. + + PRTime adjustedDate; + searchValue->GetDate(&adjustedDate); + if (whichMnemonic == m_kImapSince) + { + // it looks like the IMAP server searches on Since includes the date in question... + // our UI presents Is, IsGreater and IsLessThan. For the IsGreater case (m_kImapSince) + // we need to adjust the date so we get greater than and not greater than or equal to which + // is what the IMAP server wants to search on + // won't work on Mac. + adjustedDate += PR_USEC_PER_DAY; + } + + PRExplodedTime exploded; + PR_ExplodeTime(adjustedDate, PR_LocalTimeParameters, &exploded); + PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded); + // strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime (/* &term->m_value.u.date */ &adjustedDate)); + value = dateBuf; + } + else + { + if (attrib == nsMsgSearchAttrib::AgeInDays) + { + // okay, take the current date, subtract off the age in days, then do an appropriate Date search on + // the resulting day. + int32_t ageInDays; + + searchValue->GetAge(&ageInDays); + + PRTime now = PR_Now(); + PRTime matchDay = now - ageInDays * PR_USEC_PER_DAY; + + PRExplodedTime exploded; + PR_ExplodeTime(matchDay, PR_LocalTimeParameters, &exploded); + PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded); + // strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime (&matchDay)); + value = dateBuf; + } + else if (attrib == nsMsgSearchAttrib::Size) + { + uint32_t sizeValue; + nsAutoCString searchTermValue; + searchValue->GetSize(&sizeValue); + + // Multiply by 1024 to get into kb resolution + sizeValue *= 1024; + + // Ensure that greater than is really greater than + // in kb resolution. + if (op == nsMsgSearchOp::IsGreaterThan) + sizeValue += 1024; + + searchTermValue.AppendInt(sizeValue); + + value = ToNewCString(searchTermValue); + valueWasAllocated = true; + } + else + + if (IS_STRING_ATTRIBUTE(attrib)) + { + char16_t *convertedValue; // = reallyDredd ? MSG_EscapeSearchUrl (term->m_value.u.string) : msg_EscapeImapSearchProtocol(term->m_value.u.string); + nsString searchTermValue; + searchValue->GetStr(searchTermValue); + // Ugly switch for Korean mail/news charsets. + // We want to do this here because here is where + // we know what charset we want to use. +#ifdef DOING_CHARSET + if (reallyDredd) + dest_csid = INTL_DefaultNewsCharSetID(dest_csid); + else + dest_csid = INTL_DefaultMailCharSetID(dest_csid); +#endif + + // do all sorts of crazy escaping + convertedValue = reallyDredd ? EscapeSearchUrl (searchTermValue.get()) : + EscapeImapSearchProtocol(searchTermValue.get()); + useQuotes = ((!reallyDredd || + (nsDependentString(convertedValue).FindChar(char16_t(' ')) != -1)) && + (attrib != nsMsgSearchAttrib::Keywords)); + // now convert to char* and escape quoted_specials + nsAutoCString valueStr; + nsresult rv = ConvertFromUnicode(NS_LossyConvertUTF16toASCII(destCharset).get(), + nsDependentString(convertedValue), valueStr); + if (NS_SUCCEEDED(rv)) + { + const char *vptr = valueStr.get(); + // max escaped length is one extra character for every character in the cmd. + mozilla::UniquePtr<char[]> newValue = mozilla::MakeUnique<char[]>(2*strlen(vptr) + 1); + if (newValue) + { + char *p = newValue.get(); + while (1) + { + char ch = *vptr++; + if (!ch) + break; + if ((useQuotes ? ch == '"' : 0) || ch == '\\') + *p++ = '\\'; + *p++ = ch; + } + *p = '\0'; + value = strdup(newValue.get()); // realloc down to smaller size + } + } + else + value = strdup(""); + NS_Free(convertedValue); + valueWasAllocated = true; + + } + } + + // this should be rewritten to use nsCString + int subLen = + (value ? strlen(value) : 0) + + (useNot ? strlen(m_kImapNot) : 0) + + strlen(m_kImapHeader); + int len = strlen(whichMnemonic) + subLen + (useQuotes ? 2 : 0) + + (orHeaderMnemonic + ? (subLen + strlen(m_kImapOr) + strlen(orHeaderMnemonic) + 2 /*""*/) + : 0) + + 10; // add slough for imap string literals + char *encoding = new char[len]; + if (encoding) + { + encoding[0] = '\0'; + // Remember: if ToOrCC and useNot then the expression becomes NOT To AND Not CC as opposed to (NOT TO) || (NOT CC) + if (orHeaderMnemonic && !useNot) + PL_strcat(encoding, m_kImapOr); + if (useNot) + PL_strcat (encoding, m_kImapNot); + if (!arbitraryHeader.IsEmpty()) + PL_strcat (encoding, m_kImapHeader); + PL_strcat (encoding, whichMnemonic); + if (!ignoreValue) + err = EncodeImapValue(encoding, value, useQuotes, reallyDredd); + + if (orHeaderMnemonic) + { + if (useNot) + PL_strcat(encoding, m_kImapNot); + + PL_strcat (encoding, m_kImapHeader); + + PL_strcat (encoding, orHeaderMnemonic); + if (!ignoreValue) + err = EncodeImapValue(encoding, value, useQuotes, reallyDredd); + } + + // kmcentee, don't let the encoding end with whitespace, + // this throws off later url STRCMP + if (*encoding && *(encoding + strlen(encoding) - 1) == ' ') + *(encoding + strlen(encoding) - 1) = '\0'; + } + + if (value && valueWasAllocated) + NS_Free (value); + + *ppOutTerm = encoding; + + return err; +} + +nsresult nsMsgSearchAdapter::EncodeImapValue(char *encoding, const char *value, bool useQuotes, bool reallyDredd) +{ + // By NNTP RFC, SEARCH HEADER SUBJECT "" is legal and means 'find messages without a subject header' + if (!reallyDredd) + { + // By IMAP RFC, SEARCH HEADER SUBJECT "" is illegal and will generate an error from the server + if (!value || !value[0]) + return NS_ERROR_NULL_POINTER; + } + + if (!NS_IsAscii(value)) + { + nsAutoCString lengthStr; + PL_strcat(encoding, "{"); + lengthStr.AppendInt((int32_t) strlen(value)); + PL_strcat(encoding, lengthStr.get()); + PL_strcat(encoding, "}" CRLF); + PL_strcat(encoding, value); + return NS_OK; + } + if (useQuotes) + PL_strcat(encoding, "\""); + PL_strcat (encoding, value); + if (useQuotes) + PL_strcat(encoding, "\""); + + return NS_OK; +} + + +nsresult nsMsgSearchAdapter::EncodeImap (char **ppOutEncoding, nsISupportsArray *searchTerms, const char16_t *srcCharset, const char16_t *destCharset, bool reallyDredd) +{ + // i've left the old code (before using CBoolExpression for debugging purposes to make sure that + // the new code generates the same encoding string as the old code..... + + nsresult err = NS_OK; + *ppOutEncoding = nullptr; + + uint32_t termCount; + searchTerms->Count(&termCount); + uint32_t i = 0; + + // create our expression + nsMsgSearchBoolExpression * expression = new nsMsgSearchBoolExpression(); + if (!expression) + return NS_ERROR_OUT_OF_MEMORY; + + for (i = 0; i < termCount && NS_SUCCEEDED(err); i++) + { + char *termEncoding; + bool matchAll; + nsCOMPtr<nsIMsgSearchTerm> pTerm; + searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm), + (void **)getter_AddRefs(pTerm)); + pTerm->GetMatchAll(&matchAll); + if (matchAll) + continue; + err = EncodeImapTerm (pTerm, reallyDredd, srcCharset, destCharset, &termEncoding); + if (NS_SUCCEEDED(err) && nullptr != termEncoding) + { + expression = nsMsgSearchBoolExpression::AddSearchTerm(expression, pTerm, termEncoding); + delete [] termEncoding; + } + } + + if (NS_SUCCEEDED(err)) + { + // Catenate the intermediate encodings together into a big string + nsAutoCString encodingBuff; + + if (!reallyDredd) + encodingBuff.Append(m_kImapUnDeleted); + + expression->GenerateEncodeStr(&encodingBuff); + *ppOutEncoding = ToNewCString(encodingBuff); + } + + delete expression; + + return err; +} + + +char *nsMsgSearchAdapter::TransformSpacesToStars (const char *spaceString, msg_TransformType transformType) +{ + char *starString; + + if (transformType == kOverwrite) + { + if ((starString = strdup(spaceString)) != nullptr) + { + char *star = starString; + while ((star = PL_strchr(star, ' ')) != nullptr) + *star = '*'; + } + } + else + { + int i, count; + + for (i = 0, count = 0; spaceString[i]; ) + { + if (spaceString[i++] == ' ') + { + count++; + while (spaceString[i] && spaceString[i] == ' ') i++; + } + } + + if (transformType == kSurround) + count *= 2; + + if (count > 0) + { + if ((starString = (char *)PR_Malloc(i + count + 1)) != nullptr) + { + int j; + + for (i = 0, j = 0; spaceString[i]; ) + { + if (spaceString[i] == ' ') + { + starString[j++] = '*'; + starString[j++] = ' '; + if (transformType == kSurround) + starString[j++] = '*'; + + i++; + while (spaceString[i] && spaceString[i] == ' ') + i++; + } + else + starString[j++] = spaceString[i++]; + } + starString[j] = 0; + } + } + else + starString = strdup(spaceString); + } + + return starString; +} + +//----------------------------------------------------------------------------- +//------------------- Validity checking for menu items etc. ------------------- +//----------------------------------------------------------------------------- + +nsMsgSearchValidityTable::nsMsgSearchValidityTable () +{ + // Set everything to be unavailable and disabled + for (int i = 0; i < nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) + for (int j = 0; j < nsMsgSearchOp::kNumMsgSearchOperators; j++) + { + SetAvailable (i, j, false); + SetEnabled (i, j, false); + SetValidButNotShown (i,j, false); + } + m_numAvailAttribs = 0; // # of attributes marked with at least one available operator + // assume default is Subject, which it is for mail and news search + // it's not for LDAP, so we'll call SetDefaultAttrib() + m_defaultAttrib = nsMsgSearchAttrib::Subject; +} + +NS_IMPL_ISUPPORTS(nsMsgSearchValidityTable, nsIMsgSearchValidityTable) + + +nsresult +nsMsgSearchValidityTable::GetNumAvailAttribs(int32_t *aResult) +{ + m_numAvailAttribs = 0; + for (int i = 0; i < nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) + for (int j = 0; j < nsMsgSearchOp::kNumMsgSearchOperators; j++) { + bool available; + GetAvailable(i, j, &available); + if (available) + { + m_numAvailAttribs++; + break; + } + } + *aResult = m_numAvailAttribs; + return NS_OK; +} + +nsresult +nsMsgSearchValidityTable::ValidateTerms (nsISupportsArray *searchTerms) +{ + nsresult err = NS_OK; + uint32_t count; + + NS_ENSURE_ARG(searchTerms); + + searchTerms->Count(&count); + for (uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgSearchTerm> pTerm; + searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm), + (void **)getter_AddRefs(pTerm)); + + nsIMsgSearchTerm *iTerm = pTerm; + nsMsgSearchTerm *term = static_cast<nsMsgSearchTerm *>(iTerm); +// XP_ASSERT(term->IsValid()); + bool enabled; + bool available; + GetEnabled(term->m_attribute, term->m_operator, &enabled); + GetAvailable(term->m_attribute, term->m_operator, &available); + if (!enabled || !available) + { + bool validNotShown; + GetValidButNotShown(term->m_attribute, term->m_operator, + &validNotShown); + if (!validNotShown) + err = NS_MSG_ERROR_INVALID_SEARCH_SCOPE; + } + } + + return err; +} + +nsresult +nsMsgSearchValidityTable::GetAvailableAttributes(uint32_t *length, + nsMsgSearchAttribValue **aResult) +{ + NS_ENSURE_ARG_POINTER(length); + NS_ENSURE_ARG_POINTER(aResult); + + // count first + uint32_t totalAttributes=0; + int32_t i, j; + for (i = 0; i< nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) { + for (j=0; j< nsMsgSearchOp::kNumMsgSearchOperators; j++) { + if (m_table[i][j].bitAvailable) { + totalAttributes++; + break; + } + } + } + + nsMsgSearchAttribValue *array = (nsMsgSearchAttribValue*) + moz_xmalloc(sizeof(nsMsgSearchAttribValue) * totalAttributes); + NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY); + + uint32_t numStored=0; + for (i = 0; i< nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) { + for (j=0; j< nsMsgSearchOp::kNumMsgSearchOperators; j++) { + if (m_table[i][j].bitAvailable) { + array[numStored++] = i; + break; + } + } + } + + NS_ASSERTION(totalAttributes == numStored, "Search Attributes not lining up"); + *length = totalAttributes; + *aResult = array; + + return NS_OK; +} + +nsresult +nsMsgSearchValidityTable::GetAvailableOperators(nsMsgSearchAttribValue aAttribute, + uint32_t *aLength, + nsMsgSearchOpValue **aResult) +{ + NS_ENSURE_ARG_POINTER(aLength); + NS_ENSURE_ARG_POINTER(aResult); + + nsMsgSearchAttribValue attr; + if (aAttribute == nsMsgSearchAttrib::Default) + attr = m_defaultAttrib; + else + attr = std::min(aAttribute, + (nsMsgSearchAttribValue)nsMsgSearchAttrib::OtherHeader); + + uint32_t totalOperators=0; + int32_t i; + for (i=0; i<nsMsgSearchOp::kNumMsgSearchOperators; i++) { + if (m_table[attr][i].bitAvailable) + totalOperators++; + } + + nsMsgSearchOpValue *array = (nsMsgSearchOpValue*) + moz_xmalloc(sizeof(nsMsgSearchOpValue) * totalOperators); + NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY); + + uint32_t numStored = 0; + for (i=0; i<nsMsgSearchOp::kNumMsgSearchOperators;i++) { + if (m_table[attr][i].bitAvailable) + array[numStored++] = i; + } + + NS_ASSERTION(totalOperators == numStored, "Search Operators not lining up"); + *aLength = totalOperators; + *aResult = array; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValidityTable::SetDefaultAttrib(nsMsgSearchAttribValue aAttribute) +{ + m_defaultAttrib = aAttribute; + return NS_OK; +} + + +nsMsgSearchValidityManager::nsMsgSearchValidityManager () +{ +} + + +nsMsgSearchValidityManager::~nsMsgSearchValidityManager () +{ + // tables released by nsCOMPtr +} + +NS_IMPL_ISUPPORTS(nsMsgSearchValidityManager, nsIMsgSearchValidityManager) + +//----------------------------------------------------------------------------- +// Bottleneck accesses to the objects so we can allocate and initialize them +// lazily. This way, there's no heap overhead for the validity tables until the +// user actually searches that scope. +//----------------------------------------------------------------------------- + +NS_IMETHODIMP nsMsgSearchValidityManager::GetTable (int whichTable, nsIMsgSearchValidityTable **ppOutTable) +{ + NS_ENSURE_ARG_POINTER(ppOutTable); + + nsresult rv; + *ppOutTable = nullptr; + + nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + nsCString customHeaders; + if (NS_SUCCEEDED(rv)) + pref->GetCharPref(PREF_CUSTOM_HEADERS, getter_Copies(customHeaders)); + + switch (whichTable) + { + case nsMsgSearchScope::offlineMail: + if (!m_offlineMailTable) + rv = InitOfflineMailTable (); + if (m_offlineMailTable) + rv = SetOtherHeadersInTable(m_offlineMailTable, customHeaders.get()); + *ppOutTable = m_offlineMailTable; + break; + case nsMsgSearchScope::offlineMailFilter: + if (!m_offlineMailFilterTable) + rv = InitOfflineMailFilterTable (); + if (m_offlineMailFilterTable) + rv = SetOtherHeadersInTable(m_offlineMailFilterTable, customHeaders.get()); + *ppOutTable = m_offlineMailFilterTable; + break; + case nsMsgSearchScope::onlineMail: + if (!m_onlineMailTable) + rv = InitOnlineMailTable (); + if (m_onlineMailTable) + rv = SetOtherHeadersInTable(m_onlineMailTable, customHeaders.get()); + *ppOutTable = m_onlineMailTable; + break; + case nsMsgSearchScope::onlineMailFilter: + if (!m_onlineMailFilterTable) + rv = InitOnlineMailFilterTable (); + if (m_onlineMailFilterTable) + rv = SetOtherHeadersInTable(m_onlineMailFilterTable, customHeaders.get()); + *ppOutTable = m_onlineMailFilterTable; + break; + case nsMsgSearchScope::news: + if (!m_newsTable) + rv = InitNewsTable(); + if (m_newsTable) + rv = SetOtherHeadersInTable(m_newsTable, customHeaders.get()); + *ppOutTable = m_newsTable; + break; + case nsMsgSearchScope::newsFilter: + if (!m_newsFilterTable) + rv = InitNewsFilterTable(); + if (m_newsFilterTable) + rv = SetOtherHeadersInTable(m_newsFilterTable, customHeaders.get()); + *ppOutTable = m_newsFilterTable; + break; + case nsMsgSearchScope::localNews: + if (!m_localNewsTable) + rv = InitLocalNewsTable(); + if (m_localNewsTable) + rv = SetOtherHeadersInTable(m_localNewsTable, customHeaders.get()); + *ppOutTable = m_localNewsTable; + break; + case nsMsgSearchScope::localNewsJunk: + if (!m_localNewsJunkTable) + rv = InitLocalNewsJunkTable(); + if (m_localNewsJunkTable) + rv = SetOtherHeadersInTable(m_localNewsJunkTable, customHeaders.get()); + *ppOutTable = m_localNewsJunkTable; + break; + case nsMsgSearchScope::localNewsBody: + if (!m_localNewsBodyTable) + rv = InitLocalNewsBodyTable(); + if (m_localNewsBodyTable) + rv = SetOtherHeadersInTable(m_localNewsBodyTable, customHeaders.get()); + *ppOutTable = m_localNewsBodyTable; + break; + case nsMsgSearchScope::localNewsJunkBody: + if (!m_localNewsJunkBodyTable) + rv = InitLocalNewsJunkBodyTable(); + if (m_localNewsJunkBodyTable) + rv = SetOtherHeadersInTable(m_localNewsJunkBodyTable, customHeaders.get()); + *ppOutTable = m_localNewsJunkBodyTable; + break; + + case nsMsgSearchScope::onlineManual: + if (!m_onlineManualFilterTable) + rv = InitOnlineManualFilterTable(); + if (m_onlineManualFilterTable) + rv = SetOtherHeadersInTable(m_onlineManualFilterTable, customHeaders.get()); + *ppOutTable = m_onlineManualFilterTable; + break; + case nsMsgSearchScope::LDAP: + if (!m_ldapTable) + rv = InitLdapTable (); + *ppOutTable = m_ldapTable; + break; + case nsMsgSearchScope::LDAPAnd: + if (!m_ldapAndTable) + rv = InitLdapAndTable (); + *ppOutTable = m_ldapAndTable; + break; + case nsMsgSearchScope::LocalAB: + if (!m_localABTable) + rv = InitLocalABTable (); + *ppOutTable = m_localABTable; + break; + case nsMsgSearchScope::LocalABAnd: + if (!m_localABAndTable) + rv = InitLocalABAndTable (); + *ppOutTable = m_localABAndTable; + break; + default: + NS_ASSERTION(false, "invalid table type"); + rv = NS_MSG_ERROR_INVALID_SEARCH_TERM; + } + + NS_IF_ADDREF(*ppOutTable); + return rv; +} + +// mapping between ordered attribute values, and property strings +// see search-attributes.properties +static struct +{ + nsMsgSearchAttribValue id; + const char* property; +} +nsMsgSearchAttribMap[] = +{ + {nsMsgSearchAttrib::Subject, "Subject"}, + {nsMsgSearchAttrib::Sender, "From"}, + {nsMsgSearchAttrib::Body, "Body"}, + {nsMsgSearchAttrib::Date, "Date"}, + {nsMsgSearchAttrib::Priority, "Priority"}, + {nsMsgSearchAttrib::MsgStatus, "Status"}, + {nsMsgSearchAttrib::To, "To"}, + {nsMsgSearchAttrib::CC, "Cc"}, + {nsMsgSearchAttrib::ToOrCC, "ToOrCc"}, + {nsMsgSearchAttrib::AgeInDays, "AgeInDays"}, + {nsMsgSearchAttrib::Size, "SizeKB"}, + {nsMsgSearchAttrib::Keywords, "Tags"}, + {nsMsgSearchAttrib::Name, "AnyName"}, + {nsMsgSearchAttrib::DisplayName, "DisplayName"}, + {nsMsgSearchAttrib::Nickname, "Nickname"}, + {nsMsgSearchAttrib::ScreenName, "ScreenName"}, + {nsMsgSearchAttrib::Email, "Email"}, + {nsMsgSearchAttrib::AdditionalEmail, "AdditionalEmail"}, + {nsMsgSearchAttrib::PhoneNumber, "AnyNumber"}, + {nsMsgSearchAttrib::WorkPhone, "WorkPhone"}, + {nsMsgSearchAttrib::HomePhone, "HomePhone"}, + {nsMsgSearchAttrib::Fax, "Fax"}, + {nsMsgSearchAttrib::Pager, "Pager"}, + {nsMsgSearchAttrib::Mobile, "Mobile"}, + {nsMsgSearchAttrib::City, "City"}, + {nsMsgSearchAttrib::Street, "Street"}, + {nsMsgSearchAttrib::Title, "Title"}, + {nsMsgSearchAttrib::Organization, "Organization"}, + {nsMsgSearchAttrib::Department, "Department"}, + {nsMsgSearchAttrib::AllAddresses, "FromToCcOrBcc"}, + {nsMsgSearchAttrib::JunkScoreOrigin, "JunkScoreOrigin"}, + {nsMsgSearchAttrib::JunkPercent, "JunkPercent"}, + {nsMsgSearchAttrib::HasAttachmentStatus, "AttachmentStatus"}, + {nsMsgSearchAttrib::JunkStatus, "JunkStatus"}, + {nsMsgSearchAttrib::Label, "Label"}, + {nsMsgSearchAttrib::OtherHeader, "Customize"}, + // the last id is -1 to denote end of table + {-1, ""} +}; + +NS_IMETHODIMP +nsMsgSearchValidityManager::GetAttributeProperty(nsMsgSearchAttribValue aSearchAttribute, + nsAString& aProperty) +{ + for (int32_t i = 0; nsMsgSearchAttribMap[i].id >= 0; ++i) + { + if (nsMsgSearchAttribMap[i].id == aSearchAttribute) + { + aProperty.Assign(NS_ConvertUTF8toUTF16(nsMsgSearchAttribMap[i].property)); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +nsresult +nsMsgSearchValidityManager::NewTable(nsIMsgSearchValidityTable **aTable) +{ + NS_ENSURE_ARG_POINTER(aTable); + *aTable = new nsMsgSearchValidityTable; + if (!*aTable) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aTable); + return NS_OK; +} + +nsresult +nsMsgSearchValidityManager::SetOtherHeadersInTable (nsIMsgSearchValidityTable *aTable, const char *customHeaders) +{ + uint32_t customHeadersLength = strlen(customHeaders); + uint32_t numHeaders=0; + if (customHeadersLength) + { + nsAutoCString hdrStr(customHeaders); + hdrStr.StripWhitespace(); //remove whitespace before parsing + char *newStr = hdrStr.BeginWriting(); + char *token = NS_strtok(":", &newStr); + while(token) + { + numHeaders++; + token = NS_strtok(":", &newStr); + } + } + + NS_ASSERTION(nsMsgSearchAttrib::OtherHeader + numHeaders < nsMsgSearchAttrib::kNumMsgSearchAttributes, "more headers than the table can hold"); + + uint32_t maxHdrs = std::min(nsMsgSearchAttrib::OtherHeader + numHeaders + 1, + (uint32_t)nsMsgSearchAttrib::kNumMsgSearchAttributes); + for (uint32_t i=nsMsgSearchAttrib::OtherHeader+1;i< maxHdrs;i++) + { + aTable->SetAvailable (i, nsMsgSearchOp::Contains, 1); // added for arbitrary headers + aTable->SetEnabled (i, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable (i, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (i, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable (i, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (i, nsMsgSearchOp::Is, 1); + aTable->SetAvailable (i, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (i, nsMsgSearchOp::Isnt, 1); + } + //because custom headers can change; so reset the table for those which are no longer used. + for (uint32_t j=maxHdrs; j < nsMsgSearchAttrib::kNumMsgSearchAttributes; j++) + { + for (uint32_t k=0; k < nsMsgSearchOp::kNumMsgSearchOperators; k++) + { + aTable->SetAvailable(j,k,0); + aTable->SetEnabled(j,k,0); + } + } + return NS_OK; +} + +nsresult nsMsgSearchValidityManager::EnableDirectoryAttribute(nsIMsgSearchValidityTable *table, nsMsgSearchAttribValue aSearchAttrib) +{ + table->SetAvailable (aSearchAttrib, nsMsgSearchOp::Contains, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::Contains, 1); + table->SetAvailable (aSearchAttrib, nsMsgSearchOp::DoesntContain, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::DoesntContain, 1); + table->SetAvailable (aSearchAttrib, nsMsgSearchOp::Is, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::Is, 1); + table->SetAvailable (aSearchAttrib, nsMsgSearchOp::Isnt, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::Isnt, 1); + table->SetAvailable (aSearchAttrib, nsMsgSearchOp::BeginsWith, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::BeginsWith, 1); + table->SetAvailable (aSearchAttrib, nsMsgSearchOp::EndsWith, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::EndsWith, 1); + table->SetAvailable (aSearchAttrib, nsMsgSearchOp::SoundsLike, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::SoundsLike, 1); + return NS_OK; +} + +nsresult nsMsgSearchValidityManager::InitLdapTable() +{ + NS_ASSERTION(!m_ldapTable,"don't call this twice!"); + + nsresult rv = NewTable(getter_AddRefs(m_ldapTable)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = SetUpABTable(m_ldapTable, true); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +nsresult nsMsgSearchValidityManager::InitLdapAndTable() +{ + NS_ASSERTION(!m_ldapAndTable,"don't call this twice!"); + + nsresult rv = NewTable(getter_AddRefs(m_ldapAndTable)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = SetUpABTable(m_ldapAndTable, false); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +nsresult nsMsgSearchValidityManager::InitLocalABTable() +{ + NS_ASSERTION(!m_localABTable,"don't call this twice!"); + + nsresult rv = NewTable(getter_AddRefs(m_localABTable)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = SetUpABTable(m_localABTable, true); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +nsresult nsMsgSearchValidityManager::InitLocalABAndTable() +{ + NS_ASSERTION(!m_localABAndTable,"don't call this twice!"); + + nsresult rv = NewTable(getter_AddRefs(m_localABAndTable)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = SetUpABTable(m_localABAndTable, false); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +nsresult +nsMsgSearchValidityManager::SetUpABTable(nsIMsgSearchValidityTable *aTable, bool isOrTable) +{ + nsresult rv = aTable->SetDefaultAttrib(isOrTable ? nsMsgSearchAttrib::Name : nsMsgSearchAttrib::DisplayName); + NS_ENSURE_SUCCESS(rv,rv); + + if (isOrTable) { + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Name); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::PhoneNumber); + NS_ENSURE_SUCCESS(rv,rv); + } + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::DisplayName); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Email); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::AdditionalEmail); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::ScreenName); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Street); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::City); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Title); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Organization); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Department); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Nickname); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::WorkPhone); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::HomePhone); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Fax); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Pager); + NS_ENSURE_SUCCESS(rv,rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Mobile); + NS_ENSURE_SUCCESS(rv,rv); + + return rv; +} diff --git a/mailnews/base/search/src/nsMsgSearchImap.h b/mailnews/base/search/src/nsMsgSearchImap.h new file mode 100644 index 000000000..8f2849da6 --- /dev/null +++ b/mailnews/base/search/src/nsMsgSearchImap.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgSearchImap_h__ +#include "mozilla/Attributes.h" +#include "nsMsgSearchAdapter.h" + +//----------------------------------------------------------------------------- +//---------- Adapter class for searching online (IMAP) folders ---------------- +//----------------------------------------------------------------------------- + +class nsMsgSearchOnlineMail : public nsMsgSearchAdapter +{ +public: + nsMsgSearchOnlineMail (nsMsgSearchScopeTerm *scope, nsISupportsArray *termList); + virtual ~nsMsgSearchOnlineMail (); + + NS_IMETHOD ValidateTerms () override; + NS_IMETHOD Search (bool *aDone) override; + NS_IMETHOD GetEncoding (char **result) override; + NS_IMETHOD AddResultElement (nsIMsgDBHdr *) override; + + static nsresult Encode (nsCString& ppEncoding, + nsISupportsArray *searchTerms, + const char16_t *destCharset); + + +protected: + nsCString m_encoding; +}; + + + +#endif + diff --git a/mailnews/base/search/src/nsMsgSearchNews.cpp b/mailnews/base/search/src/nsMsgSearchNews.cpp new file mode 100644 index 000000000..72d2bd648 --- /dev/null +++ b/mailnews/base/search/src/nsMsgSearchNews.cpp @@ -0,0 +1,511 @@ +/* -*- Mode: C++; tab-width: 4; 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 "nsMsgSearchAdapter.h" +#include "nsUnicharUtils.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsMsgResultElement.h" +#include "nsMsgSearchTerm.h" +#include "nsIMsgHdr.h" +#include "nsMsgSearchNews.h" +#include "nsIDBFolderInfo.h" +#include "prprf.h" +#include "nsIMsgDatabase.h" +#include "nsMemory.h" +#include <ctype.h> +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (disable : 4996) +#endif +#include "nsISupportsArray.h" + +// Implementation of search for IMAP mail folders + + +// Implementation of search for newsgroups + + +//----------------------------------------------------------------------------- +//----------- Adapter class for searching XPAT-capable news servers ----------- +//----------------------------------------------------------------------------- + + +const char *nsMsgSearchNews::m_kNntpFrom = "FROM "; +const char *nsMsgSearchNews::m_kNntpSubject = "SUBJECT "; +const char *nsMsgSearchNews::m_kTermSeparator = "/"; + + +nsMsgSearchNews::nsMsgSearchNews (nsMsgSearchScopeTerm *scope, nsISupportsArray *termList) : nsMsgSearchAdapter (scope, termList) +{ +} + + +nsMsgSearchNews::~nsMsgSearchNews () +{ +} + + +nsresult nsMsgSearchNews::ValidateTerms () +{ + nsresult err = nsMsgSearchAdapter::ValidateTerms (); + if (NS_OK == err) + { + err = Encode (&m_encoding); + } + + return err; +} + + +nsresult nsMsgSearchNews::Search (bool *aDone) +{ + // the state machine runs in the news: handler + nsresult err = NS_ERROR_NOT_IMPLEMENTED; + return err; +} + +char16_t *nsMsgSearchNews::EncodeToWildmat (const char16_t *value) +{ + // Here we take advantage of XPAT's use of the wildmat format, which allows + // a case-insensitive match by specifying each case possibility for each character + // So, "FooBar" is encoded as "[Ff][Oo][Bb][Aa][Rr]" + + char16_t *caseInsensitiveValue = (char16_t*) moz_xmalloc(sizeof(char16_t) * ((4 * NS_strlen(value)) + 1)); + if (caseInsensitiveValue) + { + char16_t *walkValue = caseInsensitiveValue; + while (*value) + { + if (isalpha(*value)) + { + *walkValue++ = (char16_t)'['; + *walkValue++ = ToUpperCase((char16_t)*value); + *walkValue++ = ToLowerCase((char16_t)*value); + *walkValue++ = (char16_t)']'; + } + else + *walkValue++ = *value; + value++; + } + *walkValue = 0; + } + return caseInsensitiveValue; +} + + +char *nsMsgSearchNews::EncodeTerm (nsIMsgSearchTerm *term) +{ + // Develop an XPAT-style encoding for the search term + + NS_ASSERTION(term, "null term"); + if (!term) + return nullptr; + + // Find a string to represent the attribute + const char *attribEncoding = nullptr; + nsMsgSearchAttribValue attrib; + + term->GetAttrib(&attrib); + + switch (attrib) + { + case nsMsgSearchAttrib::Sender: + attribEncoding = m_kNntpFrom; + break; + case nsMsgSearchAttrib::Subject: + attribEncoding = m_kNntpSubject; + break; + default: + nsCString header; + term->GetArbitraryHeader(header); + if (header.IsEmpty()) + { + NS_ASSERTION(false,"malformed search"); // malformed search term? + return nullptr; + } + attribEncoding = header.get(); + } + + // Build a string to represent the string pattern + bool leadingStar = false; + bool trailingStar = false; + nsMsgSearchOpValue op; + term->GetOp(&op); + + switch (op) + { + case nsMsgSearchOp::Contains: + leadingStar = true; + trailingStar = true; + break; + case nsMsgSearchOp::Is: + break; + case nsMsgSearchOp::BeginsWith: + trailingStar = true; + break; + case nsMsgSearchOp::EndsWith: + leadingStar = true; + break; + default: + NS_ASSERTION(false,"malformed search"); // malformed search term? + return nullptr; + } + + // ### i18N problem Get the csid from FE, which is the correct csid for term +// int16 wincsid = INTL_GetCharSetID(INTL_DefaultTextWidgetCsidSel); + + // Do INTL_FormatNNTPXPATInRFC1522Format trick for non-ASCII string +// unsigned char *intlNonRFC1522Value = INTL_FormatNNTPXPATInNonRFC1522Format (wincsid, (unsigned char*)term->m_value.u.string); + nsCOMPtr <nsIMsgSearchValue> searchValue; + + nsresult rv = term->GetValue(getter_AddRefs(searchValue)); + if (NS_FAILED(rv) || !searchValue) + return nullptr; + + + nsString intlNonRFC1522Value; + rv = searchValue->GetStr(intlNonRFC1522Value); + if (NS_FAILED(rv) || intlNonRFC1522Value.IsEmpty()) + return nullptr; + + char16_t *caseInsensitiveValue = EncodeToWildmat (intlNonRFC1522Value.get()); + if (!caseInsensitiveValue) + return nullptr; + + // TO DO: Do INTL_FormatNNTPXPATInRFC1522Format trick for non-ASCII string + // Unfortunately, we currently do not handle xxx or xxx search in XPAT + // Need to add the INTL_FormatNNTPXPATInRFC1522Format call after we can do that + // so we should search a string in either RFC1522 format and non-RFC1522 format + + char16_t *escapedValue = EscapeSearchUrl (caseInsensitiveValue); + free(caseInsensitiveValue); + if (!escapedValue) + return nullptr; + + nsAutoCString pattern; + + if (leadingStar) + pattern.Append('*'); + pattern.Append(NS_ConvertUTF16toUTF8(escapedValue)); + if (trailingStar) + pattern.Append('*'); + + // Combine the XPAT command syntax with the attribute and the pattern to + // form the term encoding + const char xpatTemplate[] = "XPAT %s 1- %s"; + int termLength = (sizeof(xpatTemplate) - 1) + strlen(attribEncoding) + pattern.Length() + 1; + char *termEncoding = new char [termLength]; + if (termEncoding) + PR_snprintf (termEncoding, termLength, xpatTemplate, attribEncoding, pattern.get()); + + return termEncoding; +} + +nsresult nsMsgSearchNews::GetEncoding(char **result) +{ + NS_ENSURE_ARG(result); + *result = ToNewCString(m_encoding); + return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult nsMsgSearchNews::Encode (nsCString *outEncoding) +{ + NS_ASSERTION(outEncoding, "no out encoding"); + if (!outEncoding) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + + uint32_t numTerms; + + m_searchTerms->Count(&numTerms); + char **intermediateEncodings = new char * [numTerms]; + if (intermediateEncodings) + { + // Build an XPAT command for each term + int encodingLength = 0; + uint32_t i; + for (i = 0; i < numTerms; i++) + { + nsCOMPtr<nsIMsgSearchTerm> pTerm; + m_searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm), + (void **)getter_AddRefs(pTerm)); + // set boolean OR term if any of the search terms are an OR...this only works if we are using + // homogeneous boolean operators. + bool isBooleanOpAnd; + pTerm->GetBooleanAnd(&isBooleanOpAnd); + m_ORSearch = !isBooleanOpAnd; + + intermediateEncodings[i] = EncodeTerm (pTerm); + if (intermediateEncodings[i]) + encodingLength += strlen(intermediateEncodings[i]) + strlen(m_kTermSeparator); + } + encodingLength += strlen("?search"); + // Combine all the term encodings into one big encoding + char *encoding = new char [encodingLength + 1]; + if (encoding) + { + PL_strcpy (encoding, "?search"); + + m_searchTerms->Count(&numTerms); + + for (i = 0; i < numTerms; i++) + { + if (intermediateEncodings[i]) + { + PL_strcat (encoding, m_kTermSeparator); + PL_strcat (encoding, intermediateEncodings[i]); + delete [] intermediateEncodings[i]; + } + } + *outEncoding = encoding; + } + else + err = NS_ERROR_OUT_OF_MEMORY; + } + else + err = NS_ERROR_OUT_OF_MEMORY; + delete [] intermediateEncodings; + + return err; +} + +NS_IMETHODIMP nsMsgSearchNews::AddHit(nsMsgKey key) +{ + m_candidateHits.AppendElement(key); + return NS_OK; +} + +/* void CurrentUrlDone (in nsresult exitCode); */ +NS_IMETHODIMP nsMsgSearchNews::CurrentUrlDone(nsresult exitCode) +{ + CollateHits(); + ReportHits(); + return NS_OK; +} + + +#if 0 // need to switch this to a notify stop loading handler, I think. +void nsMsgSearchNews::PreExitFunction (URL_Struct * /*url*/, int status, MWContext *context) +{ + MSG_SearchFrame *frame = MSG_SearchFrame::FromContext (context); + nsMsgSearchNews *adapter = (nsMsgSearchNews*) frame->GetRunningAdapter(); + adapter->CollateHits(); + adapter->ReportHits(); + + if (status == MK_INTERRUPTED) + { + adapter->Abort(); + frame->EndCylonMode(); + } + else + { + frame->m_idxRunningScope++; + if (frame->m_idxRunningScope >= frame->m_scopeList.Count()) + frame->EndCylonMode(); + } +} +#endif // 0 + +void nsMsgSearchNews::CollateHits() +{ + // Since the XPAT commands are processed one at a time, the result set for the + // entire query is the intersection of results for each XPAT command if an AND search, + // otherwise we want the union of all the search hits (minus the duplicates of course). + + uint32_t size = m_candidateHits.Length(); + if (!size) + return; + + // Sort the article numbers first, so it's easy to tell how many hits + // on a given article we got + m_candidateHits.Sort(); + + // For an OR search we only need to count the first occurrence of a candidate. + uint32_t termCount = 1; + if (!m_ORSearch) + { + // We have a traditional AND search which must be collated. In order to + // get promoted into the hits list, a candidate article number must appear + // in the results of each XPAT command. So if we fire 3 XPAT commands (one + // per search term), the article number must appear 3 times. If it appears + // fewer than 3 times, it matched some search terms, but not all. + m_searchTerms->Count(&termCount); + } + uint32_t candidateCount = 0; + uint32_t candidate = m_candidateHits[0]; + for (uint32_t index = 0; index < size; ++index) + { + uint32_t possibleCandidate = m_candidateHits[index]; + if (candidate == possibleCandidate) + { + ++candidateCount; + } + else + { + candidateCount = 1; + candidate = possibleCandidate; + } + if (candidateCount == termCount) + m_hits.AppendElement(candidate); + } +} + +void nsMsgSearchNews::ReportHits () +{ + nsCOMPtr <nsIMsgDatabase> db; + nsCOMPtr <nsIDBFolderInfo> folderInfo; + nsCOMPtr <nsIMsgFolder> scopeFolder; + + nsresult err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + if (NS_SUCCEEDED(err) && scopeFolder) + { + err = scopeFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + } + + if (db) + { + uint32_t size = m_hits.Length(); + for (uint32_t i = 0; i < size; ++i) + { + nsCOMPtr <nsIMsgDBHdr> header; + + db->GetMsgHdrForKey(m_hits.ElementAt(i), getter_AddRefs(header)); + if (header) + ReportHit(header, scopeFolder); + } + } +} + +// ### this should take an nsIMsgFolder instead of a string location. +void nsMsgSearchNews::ReportHit (nsIMsgDBHdr *pHeaders, nsIMsgFolder *folder) +{ + // this is totally filched from msg_SearchOfflineMail until I decide whether the + // right thing is to get them from the db or from NNTP + nsCOMPtr<nsIMsgSearchSession> session; + nsCOMPtr<nsIMsgFolder> scopeFolder; + m_scope->GetFolder(getter_AddRefs(scopeFolder)); + m_scope->GetSearchSession(getter_AddRefs(session)); + if (session) + session->AddSearchHit (pHeaders, scopeFolder); +} + +nsresult nsMsgSearchValidityManager::InitNewsTable() +{ + NS_ASSERTION (nullptr == m_newsTable,"don't call this twice!"); + nsresult rv = NewTable (getter_AddRefs(m_newsTable)); + + if (NS_SUCCEEDED(rv)) + { + m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + +#if 0 + // Size should be handled after the fact... + m_newsTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); +#endif + + m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + } + + return rv; +} + +nsresult nsMsgSearchValidityManager::InitNewsFilterTable() +{ + NS_ASSERTION (nullptr == m_newsFilterTable, "news filter table already initted"); + nsresult rv = NewTable (getter_AddRefs(m_newsFilterTable)); + + if (NS_SUCCEEDED(rv)) + { + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + } + + return rv; +} diff --git a/mailnews/base/search/src/nsMsgSearchNews.h b/mailnews/base/search/src/nsMsgSearchNews.h new file mode 100644 index 000000000..5c5c1ec31 --- /dev/null +++ b/mailnews/base/search/src/nsMsgSearchNews.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgSearchNews_h__ +#include "nsMsgSearchAdapter.h" +#include "MailNewsTypes.h" +#include "nsTArray.h" + +//----------------------------------------------------------------------------- +//---------- Adapter class for searching online (news) folders ---------------- +//----------------------------------------------------------------------------- + +class nsMsgSearchNews : public nsMsgSearchAdapter +{ +public: + nsMsgSearchNews (nsMsgSearchScopeTerm *scope, nsISupportsArray *termList); + virtual ~nsMsgSearchNews (); + + NS_IMETHOD ValidateTerms () override; + NS_IMETHOD Search (bool *aDone) override; + NS_IMETHOD GetEncoding (char **result) override; + NS_IMETHOD AddHit(nsMsgKey key) override; + NS_IMETHOD CurrentUrlDone(nsresult exitCode) override; + + virtual nsresult Encode (nsCString *outEncoding); + virtual char *EncodeTerm (nsIMsgSearchTerm *); + char16_t *EncodeToWildmat (const char16_t *); + + void ReportHits (); + void CollateHits (); + void ReportHit (nsIMsgDBHdr *pHeaders, nsIMsgFolder *folder); + +protected: + nsCString m_encoding; + bool m_ORSearch; // set to true if any of the search terms contains an OR for a boolean operator. + + nsTArray<nsMsgKey> m_candidateHits; + nsTArray<nsMsgKey> m_hits; + + static const char *m_kNntpFrom; + static const char *m_kNntpSubject; + static const char *m_kTermSeparator; + static const char *m_kUrlPrefix; +}; + +#endif + diff --git a/mailnews/base/search/src/nsMsgSearchSession.cpp b/mailnews/base/search/src/nsMsgSearchSession.cpp new file mode 100644 index 000000000..422e17c40 --- /dev/null +++ b/mailnews/base/search/src/nsMsgSearchSession.cpp @@ -0,0 +1,675 @@ +/* -*- 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 "nsMsgSearchAdapter.h" +#include "nsMsgSearchBoolExpression.h" +#include "nsMsgSearchSession.h" +#include "nsMsgResultElement.h" +#include "nsMsgSearchTerm.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "nsIMsgSearchNotify.h" +#include "nsIMsgMailSession.h" +#include "nsMsgBaseCID.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgLocalSearch.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsAutoPtr.h" + +NS_IMPL_ISUPPORTS(nsMsgSearchSession, nsIMsgSearchSession, nsIUrlListener, + nsISupportsWeakReference) + +nsMsgSearchSession::nsMsgSearchSession() +{ + m_sortAttribute = nsMsgSearchAttrib::Sender; + m_idxRunningScope = 0; + m_handlingError = false; + m_expressionTree = nullptr; + m_searchPaused = false; + nsresult rv = NS_NewISupportsArray(getter_AddRefs(m_termList)); + if (NS_FAILED(rv)) + NS_ASSERTION(false, "Failed to allocate a nsISupportsArray for nsMsgFilter"); +} + +nsMsgSearchSession::~nsMsgSearchSession() +{ + InterruptSearch(); + delete m_expressionTree; + DestroyScopeList(); + DestroyTermList(); +} + +NS_IMETHODIMP +nsMsgSearchSession::AddSearchTerm(nsMsgSearchAttribValue attrib, + nsMsgSearchOpValue op, + nsIMsgSearchValue *value, + bool BooleanANDp, + const char *customString) +{ + // stupid gcc + nsMsgSearchBooleanOperator boolOp; + if (BooleanANDp) + boolOp = (nsMsgSearchBooleanOperator)nsMsgSearchBooleanOp::BooleanAND; + else + boolOp = (nsMsgSearchBooleanOperator)nsMsgSearchBooleanOp::BooleanOR; + nsMsgSearchTerm *pTerm = new nsMsgSearchTerm(attrib, op, value, + boolOp, customString); + NS_ENSURE_TRUE(pTerm, NS_ERROR_OUT_OF_MEMORY); + + m_termList->AppendElement(pTerm); + // force the expression tree to rebuild whenever we change the terms + delete m_expressionTree; + m_expressionTree = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::AppendTerm(nsIMsgSearchTerm *aTerm) +{ + NS_ENSURE_ARG_POINTER(aTerm); + NS_ENSURE_TRUE(m_termList, NS_ERROR_NOT_INITIALIZED); + delete m_expressionTree; + m_expressionTree = nullptr; + return m_termList->AppendElement(aTerm); +} + +NS_IMETHODIMP +nsMsgSearchSession::GetSearchTerms(nsISupportsArray **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = m_termList; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::CreateTerm(nsIMsgSearchTerm **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + nsMsgSearchTerm *term = new nsMsgSearchTerm; + NS_ENSURE_TRUE(term, NS_ERROR_OUT_OF_MEMORY); + + *aResult = static_cast<nsIMsgSearchTerm*>(term); + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::RegisterListener(nsIMsgSearchNotify *aListener, + int32_t aNotifyFlags) +{ + NS_ENSURE_ARG_POINTER(aListener); + m_listenerList.AppendElement(aListener); + m_listenerFlagList.AppendElement(aNotifyFlags); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::UnregisterListener(nsIMsgSearchNotify *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + size_t listenerIndex = m_listenerList.IndexOf(aListener); + if (listenerIndex != m_listenerList.NoIndex) + { + m_listenerList.RemoveElementAt(listenerIndex); + m_listenerFlagList.RemoveElementAt(listenerIndex); + + // Adjust our iterator if it is active. + // Removal of something at a higher index than the iterator does not affect + // it; we only care if the the index we were pointing at gets shifted down, + // in which case we also want to shift down. + if (m_iListener != -1 && (signed)listenerIndex <= m_iListener) + m_iListener--; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::GetNumSearchTerms(uint32_t *aNumSearchTerms) +{ + NS_ENSURE_ARG(aNumSearchTerms); + return m_termList->Count(aNumSearchTerms); +} + +NS_IMETHODIMP +nsMsgSearchSession::GetNthSearchTerm(int32_t whichTerm, + nsMsgSearchAttribValue attrib, + nsMsgSearchOpValue op, + nsIMsgSearchValue *value) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchSession::CountSearchScopes(int32_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_scopeList.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::GetNthSearchScope(int32_t which, + nsMsgSearchScopeValue *scopeId, + nsIMsgFolder **folder) +{ + NS_ENSURE_ARG_POINTER(scopeId); + NS_ENSURE_ARG_POINTER(folder); + + nsMsgSearchScopeTerm *scopeTerm = m_scopeList.SafeElementAt(which, nullptr); + NS_ENSURE_ARG(scopeTerm); + + *scopeId = scopeTerm->m_attribute; + *folder = scopeTerm->m_folder; + NS_IF_ADDREF(*folder); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::AddScopeTerm(nsMsgSearchScopeValue scope, + nsIMsgFolder *folder) +{ + if (scope != nsMsgSearchScope::allSearchableGroups) + { + NS_ASSERTION(folder, "need folder if not searching all groups"); + NS_ENSURE_TRUE(folder, NS_ERROR_NULL_POINTER); + } + + nsMsgSearchScopeTerm *pScopeTerm = new nsMsgSearchScopeTerm(this, scope, folder); + NS_ENSURE_TRUE(pScopeTerm, NS_ERROR_OUT_OF_MEMORY); + + m_scopeList.AppendElement(pScopeTerm); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::AddDirectoryScopeTerm(nsMsgSearchScopeValue scope) +{ + nsMsgSearchScopeTerm *pScopeTerm = new nsMsgSearchScopeTerm(this, scope, nullptr); + NS_ENSURE_TRUE(pScopeTerm, NS_ERROR_OUT_OF_MEMORY); + + m_scopeList.AppendElement(pScopeTerm); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::ClearScopes() +{ + DestroyScopeList(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::ScopeUsesCustomHeaders(nsMsgSearchScopeValue scope, + void *selection, + bool forFilters, + bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgSearchSession::IsStringAttribute(nsMsgSearchAttribValue attrib, + bool *_retval) +{ + // Is this check needed? + NS_ENSURE_ARG(_retval); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgSearchSession::AddAllScopes(nsMsgSearchScopeValue attrib) +{ + // don't think this is needed. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchSession::Search(nsIMsgWindow *aWindow) +{ + nsresult rv = Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgSearchNotify> listener; + m_iListener = 0; + while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length()) + { + listener = m_listenerList[m_iListener]; + int32_t listenerFlags = m_listenerFlagList[m_iListener++]; + if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onNewSearch)) + listener->OnNewSearch(); + } + m_iListener = -1; + + m_msgWindowWeak = do_GetWeakReference(aWindow); + + return BeginSearching(); +} + +NS_IMETHODIMP nsMsgSearchSession::InterruptSearch() +{ + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (msgWindow) + { + EnableFolderNotifications(true); + if (m_idxRunningScope < m_scopeList.Length()) + msgWindow->StopUrls(); + + while (m_idxRunningScope < m_scopeList.Length()) + { + ReleaseFolderDBRef(); + m_idxRunningScope++; + } + //m_idxRunningScope = m_scopeList.Length() so it will make us not run another url + } + if (m_backgroundTimer) + { + m_backgroundTimer->Cancel(); + NotifyListenersDone(NS_MSG_SEARCH_INTERRUPTED); + + m_backgroundTimer = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::PauseSearch() +{ + if (m_backgroundTimer) + { + m_backgroundTimer->Cancel(); + m_searchPaused = true; + return NS_OK; + } + else + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgSearchSession::ResumeSearch() +{ + if (m_searchPaused) + { + m_searchPaused = false; + return StartTimer(); + } + else + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgSearchSession::GetSearchParam(void **aSearchParam) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchSession::GetSearchType(nsMsgSearchType **aSearchType) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchSession::SetSearchParam(nsMsgSearchType *type, + void *param, + nsMsgSearchType **_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchSession::GetNumResults(int32_t *aNumResults) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchSession::SetWindow(nsIMsgWindow *aWindow) +{ + m_msgWindowWeak = do_GetWeakReference(aWindow); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::GetWindow(nsIMsgWindow **aWindow) +{ + NS_ENSURE_ARG_POINTER(aWindow); + *aWindow = nullptr; + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + msgWindow.swap(*aWindow); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::OnStartRunningUrl(nsIURI *url) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::OnStopRunningUrl(nsIURI *url, nsresult aExitCode) +{ + nsCOMPtr<nsIMsgSearchAdapter> runningAdapter; + + nsresult rv = GetRunningAdapter(getter_AddRefs(runningAdapter)); + // tell the current adapter that the current url has run. + if (NS_SUCCEEDED(rv) && runningAdapter) + { + runningAdapter->CurrentUrlDone(aExitCode); + EnableFolderNotifications(true); + ReleaseFolderDBRef(); + } + if (++m_idxRunningScope < m_scopeList.Length()) + DoNextSearch(); + else + NotifyListenersDone(aExitCode); + return NS_OK; +} + + +nsresult nsMsgSearchSession::Initialize() +{ + // Loop over scope terms, initializing an adapter per term. This + // architecture is necessitated by two things: + // 1. There might be more than one kind of adapter per if online + // *and* offline mail mail folders are selected, or if newsgroups + // belonging to Dredd *and* INN are selected + // 2. Most of the protocols are only capable of searching one scope at a + // time, so we'll do each scope in a separate adapter on the client + + nsMsgSearchScopeTerm *scopeTerm = nullptr; + nsresult rv = NS_OK; + + uint32_t numTerms; + m_termList->Count(&numTerms); + // Ensure that the FE has added scopes and terms to this search + NS_ASSERTION(numTerms > 0, "no terms to search!"); + if (numTerms == 0) + return NS_MSG_ERROR_NO_SEARCH_VALUES; + + // if we don't have any search scopes to search, return that code. + if (m_scopeList.Length() == 0) + return NS_MSG_ERROR_INVALID_SEARCH_SCOPE; + + m_runningUrl.Truncate(); // clear out old url, if any. + m_idxRunningScope = 0; + + // If this term list (loosely specified here by the first term) should be + // scheduled in parallel, build up a list of scopes to do the round-robin scheduling + for (uint32_t i = 0; i < m_scopeList.Length() && NS_SUCCEEDED(rv); i++) + { + scopeTerm = m_scopeList.ElementAt(i); + // NS_ASSERTION(scopeTerm->IsValid()); + + rv = scopeTerm->InitializeAdapter(m_termList); + } + + return rv; +} + +nsresult nsMsgSearchSession::BeginSearching() +{ + // Here's a sloppy way to start the URL, but I don't really have time to + // unify the scheduling mechanisms. If the first scope is a newsgroup, and + // it's not Dredd-capable, we build the URL queue. All other searches can be + // done with one URL + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (msgWindow) + msgWindow->SetStopped(false); + return DoNextSearch(); +} + +nsresult nsMsgSearchSession::DoNextSearch() +{ + nsMsgSearchScopeTerm *scope = m_scopeList.ElementAt(m_idxRunningScope); + if (scope->m_attribute == nsMsgSearchScope::onlineMail || + (scope->m_attribute == nsMsgSearchScope::news && scope->m_searchServer)) + { + nsCOMPtr<nsIMsgSearchAdapter> adapter = do_QueryInterface(scope->m_adapter); + if (adapter) + { + m_runningUrl.Truncate(); + adapter->GetEncoding(getter_Copies(m_runningUrl)); + } + NS_ENSURE_STATE(!m_runningUrl.IsEmpty()); + return GetNextUrl(); + } + else + { + return SearchWOUrls(); + } +} + +nsresult nsMsgSearchSession::GetNextUrl() +{ + nsCOMPtr<nsIMsgMessageService> msgService; + + bool stopped = false; + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (msgWindow) + msgWindow->GetStopped(&stopped); + if (stopped) + return NS_OK; + + nsMsgSearchScopeTerm *currentTerm = GetRunningScope(); + NS_ENSURE_TRUE(currentTerm, NS_ERROR_NULL_POINTER); + EnableFolderNotifications(false); + nsCOMPtr<nsIMsgFolder> folder = currentTerm->m_folder; + if (folder) + { + nsCString folderUri; + folder->GetURI(folderUri); + nsresult rv = GetMessageServiceFromURI(folderUri, getter_AddRefs(msgService)); + + if (NS_SUCCEEDED(rv) && msgService && currentTerm) + msgService->Search(this, msgWindow, currentTerm->m_folder, m_runningUrl.get()); + return rv; + } + return NS_OK; +} + +/* static */ +void nsMsgSearchSession::TimerCallback(nsITimer *aTimer, void *aClosure) +{ + NS_ENSURE_TRUE_VOID(aClosure); + nsMsgSearchSession *searchSession = (nsMsgSearchSession *) aClosure; + bool done; + bool stopped = false; + + searchSession->TimeSlice(&done); + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(searchSession->m_msgWindowWeak)); + if (msgWindow) + msgWindow->GetStopped(&stopped); + + if (done || stopped) + { + if (aTimer) + aTimer->Cancel(); + searchSession->m_backgroundTimer = nullptr; + if (searchSession->m_idxRunningScope < searchSession->m_scopeList.Length()) + searchSession->DoNextSearch(); + else + searchSession->NotifyListenersDone(NS_OK); + } +} + +nsresult nsMsgSearchSession::StartTimer() +{ + nsresult rv; + + m_backgroundTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_backgroundTimer->InitWithFuncCallback(TimerCallback, (void *) this, 0, + nsITimer::TYPE_REPEATING_SLACK); + TimerCallback(m_backgroundTimer, this); + return NS_OK; +} + +nsresult nsMsgSearchSession::SearchWOUrls() +{ + EnableFolderNotifications(false); + return StartTimer(); +} + +NS_IMETHODIMP +nsMsgSearchSession::GetRunningAdapter(nsIMsgSearchAdapter **aSearchAdapter) +{ + NS_ENSURE_ARG_POINTER(aSearchAdapter); + *aSearchAdapter = nullptr; + nsMsgSearchScopeTerm *scope = GetRunningScope(); + if (scope) + { + NS_IF_ADDREF(*aSearchAdapter = scope->m_adapter); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::AddSearchHit(nsIMsgDBHdr *aHeader, + nsIMsgFolder *aFolder) +{ + nsCOMPtr<nsIMsgSearchNotify> listener; + m_iListener = 0; + while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length()) + { + listener = m_listenerList[m_iListener]; + int32_t listenerFlags = m_listenerFlagList[m_iListener++]; + if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onSearchHit)) + listener->OnSearchHit(aHeader, aFolder); + } + m_iListener = -1; + return NS_OK; +} + +nsresult nsMsgSearchSession::NotifyListenersDone(nsresult aStatus) +{ + // need to stabilize "this" in case one of the listeners releases the last + // reference to us. + RefPtr<nsIMsgSearchSession> kungFuDeathGrip(this); + + nsCOMPtr<nsIMsgSearchNotify> listener; + m_iListener = 0; + while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length()) + { + listener = m_listenerList[m_iListener]; + int32_t listenerFlags = m_listenerFlagList[m_iListener++]; + if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onSearchDone)) + listener->OnSearchDone(aStatus); + } + m_iListener = -1; + return NS_OK; +} + +void nsMsgSearchSession::DestroyScopeList() +{ + nsMsgSearchScopeTerm *scope = nullptr; + + for (int32_t i = m_scopeList.Length() - 1; i >= 0; i--) + { + scope = m_scopeList.ElementAt(i); + // NS_ASSERTION (scope->IsValid(), "invalid search scope"); + if (scope->m_adapter) + scope->m_adapter->ClearScope(); + } + m_scopeList.Clear(); +} + + +void nsMsgSearchSession::DestroyTermList() +{ + m_termList->Clear(); +} + +nsMsgSearchScopeTerm *nsMsgSearchSession::GetRunningScope() +{ + return m_scopeList.SafeElementAt(m_idxRunningScope, nullptr); +} + +nsresult nsMsgSearchSession::TimeSlice(bool *aDone) +{ + // we only do serial for now. + return TimeSliceSerial(aDone); +} + +void nsMsgSearchSession::ReleaseFolderDBRef() +{ + nsMsgSearchScopeTerm *scope = GetRunningScope(); + if (!scope) + return; + + bool isOpen = false; + uint32_t flags; + nsCOMPtr<nsIMsgFolder> folder; + scope->GetFolder(getter_AddRefs(folder)); + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID); + if (!mailSession || !folder) + return; + + mailSession->IsFolderOpenInWindow(folder, &isOpen); + folder->GetFlags(&flags); + + /*we don't null out the db reference for inbox because inbox is like the "main" folder + and performance outweighs footprint */ + if (!isOpen && !(nsMsgFolderFlags::Inbox & flags)) + folder->SetMsgDatabase(nullptr); +} +nsresult nsMsgSearchSession::TimeSliceSerial(bool *aDone) +{ + // This version of TimeSlice runs each scope term one at a time, and waits until one + // scope term is finished before starting another one. When we're searching the local + // disk, this is the fastest way to do it. + + NS_ENSURE_ARG_POINTER(aDone); + + nsMsgSearchScopeTerm *scope = GetRunningScope(); + if (!scope) + { + *aDone = true; + return NS_OK; + } + + nsresult rv = scope->TimeSlice(aDone); + if (*aDone || NS_FAILED(rv)) + { + EnableFolderNotifications(true); + ReleaseFolderDBRef(); + m_idxRunningScope++; + EnableFolderNotifications(false); + // check if the next scope is an online search; if so, + // set *aDone to true so that we'll try to run the next + // search in TimerCallback. + scope = GetRunningScope(); + if (scope && (scope->m_attribute == nsMsgSearchScope::onlineMail || + (scope->m_attribute == nsMsgSearchScope::news && scope->m_searchServer))) + { + *aDone = true; + return rv; + } + } + *aDone = false; + return rv; +} + +void +nsMsgSearchSession::EnableFolderNotifications(bool aEnable) +{ + nsMsgSearchScopeTerm *scope = GetRunningScope(); + if (scope) + { + nsCOMPtr<nsIMsgFolder> folder; + scope->GetFolder(getter_AddRefs(folder)); + if (folder) //enable msg count notifications + folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, aEnable, false); + } +} + +//this method is used for adding new hdrs to quick search view +NS_IMETHODIMP +nsMsgSearchSession::MatchHdr(nsIMsgDBHdr *aMsgHdr, nsIMsgDatabase *aDatabase, bool *aResult) +{ + nsMsgSearchScopeTerm *scope = m_scopeList.SafeElementAt(0, nullptr); + if (scope) + { + if (!scope->m_adapter) + scope->InitializeAdapter(m_termList); + if (scope->m_adapter) + { + nsAutoString nullCharset, folderCharset; + scope->m_adapter->GetSearchCharsets(nullCharset, folderCharset); + NS_ConvertUTF16toUTF8 charset(folderCharset.get()); + nsMsgSearchOfflineMail::MatchTermsForSearch(aMsgHdr, m_termList, + charset.get(), scope, aDatabase, &m_expressionTree, aResult); + } + } + return NS_OK; +} diff --git a/mailnews/base/search/src/nsMsgSearchSession.h b/mailnews/base/search/src/nsMsgSearchSession.h new file mode 100644 index 000000000..d5d62654f --- /dev/null +++ b/mailnews/base/search/src/nsMsgSearchSession.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsMsgSearchSession_h___ +#define nsMsgSearchSession_h___ + +#include "nscore.h" +#include "nsMsgSearchCore.h" +#include "nsIMsgSearchSession.h" +#include "nsIUrlListener.h" +#include "nsIMsgWindow.h" +#include "nsITimer.h" +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (disable : 4996) +#endif +#include "nsISupportsArray.h" +#include "nsCOMArray.h" +#include "nsWeakReference.h" +#include "nsTObserverArray.h" + +class nsMsgSearchAdapter; +class nsMsgSearchBoolExpression; +class nsMsgSearchScopeTerm; + +class nsMsgSearchSession : public nsIMsgSearchSession, public nsIUrlListener, public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSEARCHSESSION + NS_DECL_NSIURLLISTENER + + nsMsgSearchSession(); + +protected: + virtual ~nsMsgSearchSession(); + + nsWeakPtr m_msgWindowWeak; + nsresult Initialize(); + nsresult StartTimer(); + nsresult TimeSlice (bool *aDone); + nsMsgSearchScopeTerm *GetRunningScope(); + void StopRunning(); + nsresult BeginSearching(); + nsresult DoNextSearch(); + nsresult SearchWOUrls (); + nsresult GetNextUrl(); + nsresult NotifyListenersDone(nsresult status); + void EnableFolderNotifications(bool aEnable); + void ReleaseFolderDBRef(); + + nsTArray<RefPtr<nsMsgSearchScopeTerm>> m_scopeList; + nsCOMPtr <nsISupportsArray> m_termList; + + nsTArray<nsCOMPtr<nsIMsgSearchNotify> > m_listenerList; + nsTArray<int32_t> m_listenerFlagList; + /** + * Iterator index for m_listenerList/m_listenerFlagList. We used to use an + * nsTObserverArray for m_listenerList but its auto-adjusting iterator was + * not helping us keep our m_listenerFlagList iterator correct. + * + * We are making the simplifying assumption that our notifications are + * non-reentrant. In the exceptional case that it turns out they are + * reentrant, we assume that this is the result of canceling a search while + * the session is active and initiating a new one. In that case, we assume + * the outer iteration can safely be abandoned. + * + * This value is defined to be the index of the next listener we will process. + * This allows us to use the sentinel value of -1 to convey that no iteration + * is in progress (and the iteration process to abort if the value transitions + * to -1, which we always set on conclusion of our loop). + */ + int32_t m_iListener; + + void DestroyTermList (); + void DestroyScopeList (); + + static void TimerCallback(nsITimer *aTimer, void *aClosure); + // support for searching multiple scopes in serial + nsresult TimeSliceSerial (bool *aDone); + nsresult TimeSliceParallel (); + + nsMsgSearchAttribValue m_sortAttribute; + uint32_t m_idxRunningScope; + nsMsgSearchType m_searchType; + bool m_handlingError; + nsCString m_runningUrl; // The url for the current search + nsCOMPtr <nsITimer> m_backgroundTimer; + bool m_searchPaused; + nsMsgSearchBoolExpression *m_expressionTree; +}; + +#endif diff --git a/mailnews/base/search/src/nsMsgSearchTerm.cpp b/mailnews/base/search/src/nsMsgSearchTerm.cpp new file mode 100644 index 000000000..139ab7b20 --- /dev/null +++ b/mailnews/base/search/src/nsMsgSearchTerm.cpp @@ -0,0 +1,2088 @@ +/* -*- 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 "prmem.h" +#include "nsMsgSearchCore.h" +#include "nsIMsgSearchSession.h" +#include "nsMsgUtils.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsMsgSearchTerm.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsMsgBodyHandler.h" +#include "nsMsgResultElement.h" +#include "nsIMsgImapMailFolder.h" +#include "nsMsgSearchImap.h" +#include "nsMsgLocalSearch.h" +#include "nsMsgSearchNews.h" +#include "nsMsgSearchValue.h" +#include "nsMsgI18N.h" +#include "nsIMimeConverter.h" +#include "nsMsgMimeCID.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIMsgFilterPlugin.h" +#include "nsIFile.h" +#include "nsISeekableStream.h" +#include "nsNetCID.h" +#include "nsIFileStreams.h" +#include "nsUnicharUtils.h" +#include "nsIAbCard.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include <ctype.h> +#include "nsMsgBaseCID.h" +#include "nsIMsgTagService.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgFilterService.h" +#include "nsIMsgPluggableStore.h" +#include "nsAbBaseCID.h" +#include "nsIAbManager.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/mailnews/MimeHeaderParser.h" + +using namespace mozilla::mailnews; + +//--------------------------------------------------------------------------- +// nsMsgSearchTerm specifies one criterion, e.g. name contains phil +//--------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +//-------------------- Implementation of nsMsgSearchTerm ----------------------- +//----------------------------------------------------------------------------- +#define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders" + +typedef struct +{ + nsMsgSearchAttribValue attrib; + const char *attribName; +} nsMsgSearchAttribEntry; + +nsMsgSearchAttribEntry SearchAttribEntryTable[] = +{ + {nsMsgSearchAttrib::Subject, "subject"}, + {nsMsgSearchAttrib::Sender, "from"}, + {nsMsgSearchAttrib::Body, "body"}, + {nsMsgSearchAttrib::Date, "date"}, + {nsMsgSearchAttrib::Priority, "priority"}, + {nsMsgSearchAttrib::MsgStatus, "status"}, + {nsMsgSearchAttrib::To, "to"}, + {nsMsgSearchAttrib::CC, "cc"}, + {nsMsgSearchAttrib::ToOrCC, "to or cc"}, + {nsMsgSearchAttrib::AllAddresses, "all addresses"}, + {nsMsgSearchAttrib::AgeInDays, "age in days"}, + {nsMsgSearchAttrib::Label, "label"}, + {nsMsgSearchAttrib::Keywords, "tag"}, + {nsMsgSearchAttrib::Size, "size"}, + // this used to be nsMsgSearchAttrib::SenderInAddressBook + // we used to have two Sender menuitems + // for backward compatability, we can still parse + // the old style. see bug #179803 + {nsMsgSearchAttrib::Sender, "from in ab"}, + {nsMsgSearchAttrib::JunkStatus, "junk status"}, + {nsMsgSearchAttrib::JunkPercent, "junk percent"}, + {nsMsgSearchAttrib::JunkScoreOrigin, "junk score origin"}, + {nsMsgSearchAttrib::HasAttachmentStatus, "has attachment status"}, +}; + +static const unsigned int sNumSearchAttribEntryTable = + MOZ_ARRAY_LENGTH(SearchAttribEntryTable); + +// Take a string which starts off with an attribute +// and return the matching attribute. If the string is not in the table, and it +// begins with a quote, then we can conclude that it is an arbitrary header. +// Otherwise if not in the table, it is the id for a custom search term. +nsresult NS_MsgGetAttributeFromString(const char *string, nsMsgSearchAttribValue *attrib, + nsACString &aCustomId) +{ + NS_ENSURE_ARG_POINTER(string); + NS_ENSURE_ARG_POINTER(attrib); + + bool found = false; + bool isArbitraryHeader = false; + // arbitrary headers have a leading quote + if (*string != '"') + { + for (unsigned int idxAttrib = 0; idxAttrib < sNumSearchAttribEntryTable; idxAttrib++) + { + if (!PL_strcasecmp(string, SearchAttribEntryTable[idxAttrib].attribName)) + { + found = true; + *attrib = SearchAttribEntryTable[idxAttrib].attrib; + break; + } + } + } + else // remove the leading quote + { + string++; + isArbitraryHeader = true; + } + + if (!found && !isArbitraryHeader) + { + // must be a custom attribute + *attrib = nsMsgSearchAttrib::Custom; + aCustomId.Assign(string); + return NS_OK; + } + + if (!found) + { + nsresult rv; + bool goodHdr; + IsRFC822HeaderFieldName(string, &goodHdr); + if (!goodHdr) + return NS_MSG_INVALID_CUSTOM_HEADER; + //49 is for showing customize... in ui, headers start from 50 onwards up until 99. + *attrib = nsMsgSearchAttrib::OtherHeader+1; + + nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefService->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString headers; + prefBranch->GetCharPref(MAILNEWS_CUSTOM_HEADERS, getter_Copies(headers)); + + if (!headers.IsEmpty()) + { + nsAutoCString hdrStr(headers); + hdrStr.StripWhitespace(); //remove whitespace before parsing + + char *newStr= hdrStr.BeginWriting(); + char *token = NS_strtok(":", &newStr); + uint32_t i=0; + while (token) + { + if (PL_strcasecmp(token, string) == 0) + { + *attrib += i; //we found custom header in the pref + found = true; + break; + } + token = NS_strtok(":", &newStr); + i++; + } + } + } + // If we didn't find the header in MAILNEWS_CUSTOM_HEADERS, we're + // going to return NS_OK and an attrib of nsMsgSearchAttrib::OtherHeader+1. + // in case it's a client side spam filter description filter, + // which doesn't add its headers to mailnews.customMailHeaders. + // We've already checked that it's a valid header and returned + // an error if so. + + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchTerm::GetAttributeFromString(const char *aString, + nsMsgSearchAttribValue *aAttrib) +{ + nsAutoCString customId; + return NS_MsgGetAttributeFromString(aString, aAttrib, customId); +} + +nsresult NS_MsgGetStringForAttribute(int16_t attrib, const char **string) +{ + NS_ENSURE_ARG_POINTER(string); + + bool found = false; + for (unsigned int idxAttrib = 0; idxAttrib < sNumSearchAttribEntryTable; idxAttrib++) + { + // I'm using the idx's as aliases into MSG_SearchAttribute and + // MSG_SearchOperator enums which is legal because of the way the + // enums are defined (starts at 0, numItems at end) + if (attrib == SearchAttribEntryTable[idxAttrib].attrib) + { + found = true; + *string = SearchAttribEntryTable[idxAttrib].attribName; + break; + } + } + if (!found) + *string = ""; // don't leave the string uninitialized + + // we no longer return invalid attribute. If we cannot find the string in the table, + // then it is an arbitrary header. Return success regardless if found or not + return NS_OK; +} + +typedef struct +{ + nsMsgSearchOpValue op; + const char *opName; +} nsMsgSearchOperatorEntry; + +nsMsgSearchOperatorEntry SearchOperatorEntryTable[] = +{ + {nsMsgSearchOp::Contains, "contains"}, + {nsMsgSearchOp::DoesntContain,"doesn't contain"}, + {nsMsgSearchOp::Is,"is"}, + {nsMsgSearchOp::Isnt, "isn't"}, + {nsMsgSearchOp::IsEmpty, "is empty"}, + {nsMsgSearchOp::IsntEmpty, "isn't empty"}, + {nsMsgSearchOp::IsBefore, "is before"}, + {nsMsgSearchOp::IsAfter, "is after"}, + {nsMsgSearchOp::IsHigherThan, "is higher than"}, + {nsMsgSearchOp::IsLowerThan, "is lower than"}, + {nsMsgSearchOp::BeginsWith, "begins with"}, + {nsMsgSearchOp::EndsWith, "ends with"}, + {nsMsgSearchOp::IsInAB, "is in ab"}, + {nsMsgSearchOp::IsntInAB, "isn't in ab"}, + {nsMsgSearchOp::IsGreaterThan, "is greater than"}, + {nsMsgSearchOp::IsLessThan, "is less than"}, + {nsMsgSearchOp::Matches, "matches"}, + {nsMsgSearchOp::DoesntMatch, "doesn't match"} +}; + +static const unsigned int sNumSearchOperatorEntryTable = + MOZ_ARRAY_LENGTH(SearchOperatorEntryTable); + +nsresult NS_MsgGetOperatorFromString(const char *string, int16_t *op) +{ + NS_ENSURE_ARG_POINTER(string); + NS_ENSURE_ARG_POINTER(op); + + bool found = false; + for (unsigned int idxOp = 0; idxOp < sNumSearchOperatorEntryTable; idxOp++) + { + // I'm using the idx's as aliases into MSG_SearchAttribute and + // MSG_SearchOperator enums which is legal because of the way the + // enums are defined (starts at 0, numItems at end) + if (!PL_strcasecmp(string, SearchOperatorEntryTable[idxOp].opName)) + { + found = true; + *op = SearchOperatorEntryTable[idxOp].op; + break; + } + } + return (found) ? NS_OK : NS_ERROR_INVALID_ARG; +} + +nsresult NS_MsgGetStringForOperator(int16_t op, const char **string) +{ + NS_ENSURE_ARG_POINTER(string); + + bool found = false; + for (unsigned int idxOp = 0; idxOp < sNumSearchOperatorEntryTable; idxOp++) + { + // I'm using the idx's as aliases into MSG_SearchAttribute and + // MSG_SearchOperator enums which is legal because of the way the + // enums are defined (starts at 0, numItems at end) + if (op == SearchOperatorEntryTable[idxOp].op) + { + found = true; + *string = SearchOperatorEntryTable[idxOp].opName; + break; + } + } + + return (found) ? NS_OK : NS_ERROR_INVALID_ARG; +} + +void NS_MsgGetUntranslatedStatusName (uint32_t s, nsCString *outName) +{ + const char *tmpOutName = NULL; +#define MSG_STATUS_MASK (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |\ + nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::New | nsMsgMessageFlags::Marked) + uint32_t maskOut = (s & MSG_STATUS_MASK); + + // diddle the flags to pay attention to the most important ones first, if multiple + // flags are set. Should remove this code from the winfe. + if (maskOut & nsMsgMessageFlags::New) + maskOut = nsMsgMessageFlags::New; + if (maskOut & nsMsgMessageFlags::Replied && + maskOut & nsMsgMessageFlags::Forwarded) + maskOut = nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded; + else if (maskOut & nsMsgMessageFlags::Forwarded) + maskOut = nsMsgMessageFlags::Forwarded; + else if (maskOut & nsMsgMessageFlags::Replied) + maskOut = nsMsgMessageFlags::Replied; + + switch (maskOut) + { + case nsMsgMessageFlags::Read: + tmpOutName = "read"; + break; + case nsMsgMessageFlags::Replied: + tmpOutName = "replied"; + break; + case nsMsgMessageFlags::Forwarded: + tmpOutName = "forwarded"; + break; + case nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied: + tmpOutName = "replied and forwarded"; + break; + case nsMsgMessageFlags::New: + tmpOutName = "new"; + break; + case nsMsgMessageFlags::Marked: + tmpOutName = "flagged"; + break; + default: + // This is fine, status may be "unread" for example + break; + } + + if (tmpOutName) + *outName = tmpOutName; +} + + +int32_t NS_MsgGetStatusValueFromName(char *name) +{ + if (!strcmp("read", name)) + return nsMsgMessageFlags::Read; + if (!strcmp("replied", name)) + return nsMsgMessageFlags::Replied; + if (!strcmp("forwarded", name)) + return nsMsgMessageFlags::Forwarded; + if (!strcmp("replied and forwarded", name)) + return nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied; + if (!strcmp("new", name)) + return nsMsgMessageFlags::New; + if (!strcmp("flagged", name)) + return nsMsgMessageFlags::Marked; + return 0; +} + + +// Needed for DeStream method. +nsMsgSearchTerm::nsMsgSearchTerm() +{ + // initialize this to zero + m_value.string=nullptr; + m_value.attribute=0; + m_value.u.priority=0; + m_attribute = nsMsgSearchAttrib::Default; + m_operator = nsMsgSearchOp::Contains; + mBeginsGrouping = false; + mEndsGrouping = false; + m_matchAll = false; + + // valgrind warning during GC/java data check suggests + // m_booleanp needs to be initialized too. + m_booleanOp = nsMsgSearchBooleanOp::BooleanAND; +} + +nsMsgSearchTerm::nsMsgSearchTerm ( + nsMsgSearchAttribValue attrib, + nsMsgSearchOpValue op, + nsIMsgSearchValue *val, + nsMsgSearchBooleanOperator boolOp, + const char * aCustomString) +{ + m_operator = op; + m_attribute = attrib; + m_booleanOp = boolOp; + if (attrib > nsMsgSearchAttrib::OtherHeader && attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes && aCustomString) + { + m_arbitraryHeader = aCustomString; + ToLowerCaseExceptSpecials(m_arbitraryHeader); + } + else if (attrib == nsMsgSearchAttrib::Custom) + { + m_customId = aCustomString; + } + + nsMsgResultElement::AssignValues (val, &m_value); + m_matchAll = false; +} + + + +nsMsgSearchTerm::~nsMsgSearchTerm () +{ + if (IS_STRING_ATTRIBUTE (m_attribute) && m_value.string) + NS_Free(m_value.string); +} + +NS_IMPL_ISUPPORTS(nsMsgSearchTerm, nsIMsgSearchTerm) + + +// Perhaps we could find a better place for this? +// Caller needs to free. +/* static */char *nsMsgSearchTerm::EscapeQuotesInStr(const char *str) +{ + int numQuotes = 0; + for (const char *strPtr = str; *strPtr; strPtr++) + if (*strPtr == '"') + numQuotes++; + int escapedStrLen = PL_strlen(str) + numQuotes; + char *escapedStr = (char *) PR_Malloc(escapedStrLen + 1); + if (escapedStr) + { + char *destPtr; + for (destPtr = escapedStr; *str; str++) + { + if (*str == '"') + *destPtr++ = '\\'; + *destPtr++ = *str; + } + *destPtr = '\0'; + } + return escapedStr; +} + + +nsresult nsMsgSearchTerm::OutputValue(nsCString &outputStr) +{ + if (IS_STRING_ATTRIBUTE(m_attribute) && m_value.string) + { + bool quoteVal = false; + // need to quote strings with ')' and strings starting with '"' or ' ' + // filter code will escape quotes + if (PL_strchr(m_value.string, ')') || + (m_value.string[0] == ' ') || + (m_value.string[0] == '"')) + { + quoteVal = true; + outputStr += "\""; + } + if (PL_strchr(m_value.string, '"')) + { + char *escapedString = nsMsgSearchTerm::EscapeQuotesInStr(m_value.string); + if (escapedString) + { + outputStr += escapedString; + PR_Free(escapedString); + } + + } + else + { + outputStr += m_value.string; + } + if (quoteVal) + outputStr += "\""; + } + else + { + switch (m_attribute) + { + case nsMsgSearchAttrib::Date: + { + PRExplodedTime exploded; + PR_ExplodeTime(m_value.u.date, PR_LocalTimeParameters, &exploded); + + // wow, so tm_mon is 0 based, tm_mday is 1 based. + char dateBuf[100]; + PR_FormatTimeUSEnglish (dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded); + outputStr += dateBuf; + break; + } + case nsMsgSearchAttrib::AgeInDays: + { + outputStr.AppendInt(m_value.u.age); + break; + } + case nsMsgSearchAttrib::Label: + { + outputStr.AppendInt(m_value.u.label); + break; + } + case nsMsgSearchAttrib::JunkStatus: + { + outputStr.AppendInt(m_value.u.junkStatus); // only if we write to disk, right? + break; + } + case nsMsgSearchAttrib::JunkPercent: + { + outputStr.AppendInt(m_value.u.junkPercent); + break; + } + case nsMsgSearchAttrib::MsgStatus: + { + nsAutoCString status; + NS_MsgGetUntranslatedStatusName (m_value.u.msgStatus, &status); + outputStr += status; + break; + } + case nsMsgSearchAttrib::Priority: + { + nsAutoCString priority; + NS_MsgGetUntranslatedPriorityName(m_value.u.priority, priority); + outputStr += priority; + break; + } + case nsMsgSearchAttrib::HasAttachmentStatus: + { + outputStr.Append("true"); // don't need anything here, really + break; + } + case nsMsgSearchAttrib::Size: + { + outputStr.AppendInt(m_value.u.size); + break; + } + case nsMsgSearchAttrib::Uint32HdrProperty: + { + outputStr.AppendInt(m_value.u.msgStatus); + break; + } + default: + NS_ASSERTION(false, "trying to output invalid attribute"); + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchTerm::GetTermAsString (nsACString &outStream) +{ + const char *operatorStr; + nsAutoCString outputStr; + nsresult rv; + + if (m_matchAll) + { + outStream = "ALL"; + return NS_OK; + } + + // if arbitrary header, use it instead! + if (m_attribute > nsMsgSearchAttrib::OtherHeader && + m_attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes) + { + outputStr = "\""; + outputStr += m_arbitraryHeader; + outputStr += "\""; + } + else if (m_attribute == nsMsgSearchAttrib::Custom) + { + // use the custom id as the string + outputStr = m_customId; + } + + else if (m_attribute == nsMsgSearchAttrib::Uint32HdrProperty) + { + outputStr = m_hdrProperty; + } + else { + const char *attrib; + rv = NS_MsgGetStringForAttribute(m_attribute, &attrib); + NS_ENSURE_SUCCESS(rv, rv); + + outputStr = attrib; + } + + outputStr += ','; + + rv = NS_MsgGetStringForOperator(m_operator, &operatorStr); + NS_ENSURE_SUCCESS(rv, rv); + + outputStr += operatorStr; + outputStr += ','; + + OutputValue(outputStr); + outStream = outputStr; + return NS_OK; +} + +// fill in m_value from the input stream. +nsresult nsMsgSearchTerm::ParseValue(char *inStream) +{ + if (IS_STRING_ATTRIBUTE(m_attribute)) + { + bool quoteVal = false; + while (isspace(*inStream)) + inStream++; + // need to remove pair of '"', if present + if (*inStream == '"') + { + quoteVal = true; + inStream++; + } + int valueLen = PL_strlen(inStream); + if (quoteVal && inStream[valueLen - 1] == '"') + valueLen--; + + m_value.string = (char *) PR_Malloc(valueLen + 1); + PL_strncpy(m_value.string, inStream, valueLen + 1); + m_value.string[valueLen] = '\0'; + CopyUTF8toUTF16(m_value.string, m_value.utf16String); + } + else + { + switch (m_attribute) + { + case nsMsgSearchAttrib::Date: + PR_ParseTimeString (inStream, false, &m_value.u.date); + break; + case nsMsgSearchAttrib::MsgStatus: + m_value.u.msgStatus = NS_MsgGetStatusValueFromName(inStream); + break; + case nsMsgSearchAttrib::Priority: + NS_MsgGetPriorityFromString(inStream, m_value.u.priority); + break; + case nsMsgSearchAttrib::AgeInDays: + m_value.u.age = atoi(inStream); + break; + case nsMsgSearchAttrib::Label: + m_value.u.label = atoi(inStream); + break; + case nsMsgSearchAttrib::JunkStatus: + m_value.u.junkStatus = atoi(inStream); // only if we read from disk, right? + break; + case nsMsgSearchAttrib::JunkPercent: + m_value.u.junkPercent = atoi(inStream); + break; + case nsMsgSearchAttrib::HasAttachmentStatus: + m_value.u.msgStatus = nsMsgMessageFlags::Attachment; + break; // this should always be true. + case nsMsgSearchAttrib::Size: + m_value.u.size = atoi(inStream); + break; + default: + NS_ASSERTION(false, "invalid attribute parsing search term value"); + break; + } + } + m_value.attribute = m_attribute; + return NS_OK; +} + +// find the operator code for this operator string. +nsresult +nsMsgSearchTerm::ParseOperator(char *inStream, nsMsgSearchOpValue *value) +{ + NS_ENSURE_ARG_POINTER(value); + int16_t operatorVal; + while (isspace(*inStream)) + inStream++; + + char *commaSep = PL_strchr(inStream, ','); + + if (commaSep) + *commaSep = '\0'; + + nsresult err = NS_MsgGetOperatorFromString(inStream, &operatorVal); + *value = (nsMsgSearchOpValue) operatorVal; + return err; +} + +// find the attribute code for this comma-delimited attribute. +nsresult +nsMsgSearchTerm::ParseAttribute(char *inStream, nsMsgSearchAttribValue *attrib) +{ + while (isspace(*inStream)) + inStream++; + + // if we are dealing with an arbitrary header, it will be quoted.... + // it seems like a kludge, but to distinguish arbitrary headers from + // standard headers with the same name, like "Date", we'll use the + // presence of the quote to recognize arbitrary headers. We leave the + // leading quote as a flag, but remove the trailing quote. + bool quoteVal = false; + if (*inStream == '"') + quoteVal = true; + + // arbitrary headers are quoted. Skip first character, which will be the + // first quote for arbitrary headers + char *separator = strchr(inStream + 1, quoteVal ? '"' : ','); + + if (separator) + *separator = '\0'; + + nsAutoCString customId; + nsresult rv = NS_MsgGetAttributeFromString(inStream, attrib, m_customId); + NS_ENSURE_SUCCESS(rv, rv); + + if (*attrib > nsMsgSearchAttrib::OtherHeader && *attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) // if we are dealing with an arbitrary header.... + { + m_arbitraryHeader = inStream + 1; // remove the leading quote + ToLowerCaseExceptSpecials(m_arbitraryHeader); + } + return rv; +} + +// De stream one search term. If the condition looks like +// condition = "(to or cc, contains, r-thompson) AND (body, doesn't contain, fred)" +// This routine should get called twice, the first time +// with "to or cc, contains, r-thompson", the second time with +// "body, doesn't contain, fred" + +nsresult nsMsgSearchTerm::DeStreamNew (char *inStream, int16_t /*length*/) +{ + if (!strcmp(inStream, "ALL")) + { + m_matchAll = true; + return NS_OK; + } + char *commaSep = PL_strchr(inStream, ','); + nsresult rv = ParseAttribute(inStream, &m_attribute); // will allocate space for arbitrary header if necessary + NS_ENSURE_SUCCESS(rv, rv); + if (!commaSep) + return NS_ERROR_INVALID_ARG; + char *secondCommaSep = PL_strchr(commaSep + 1, ','); + if (commaSep) + rv = ParseOperator(commaSep + 1, &m_operator); + NS_ENSURE_SUCCESS(rv, rv); + // convert label filters and saved searches to keyword equivalents + if (secondCommaSep) + ParseValue(secondCommaSep + 1); + if (m_attribute == nsMsgSearchAttrib::Label) + { + nsAutoCString keyword("$label"); + m_value.attribute = m_attribute = nsMsgSearchAttrib::Keywords; + keyword.Append('0' + m_value.u.label); + m_value.string = PL_strdup(keyword.get()); + CopyUTF8toUTF16(m_value.string, m_value.utf16String); + } + return NS_OK; +} + + +// Looks in the MessageDB for the user specified arbitrary header, if it finds the header, it then looks for a match against +// the value for the header. +nsresult nsMsgSearchTerm::MatchArbitraryHeader (nsIMsgSearchScopeTerm *scope, + uint32_t length /* in lines*/, + const char *charset, + bool charsetOverride, + nsIMsgDBHdr *msg, + nsIMsgDatabase* db, + const char * headers, + uint32_t headersSize, + bool ForFiltering, + bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + *pResult = false; + nsresult rv = NS_OK; + bool matchExpected = m_operator == nsMsgSearchOp::Contains || + m_operator == nsMsgSearchOp::Is || + m_operator == nsMsgSearchOp::BeginsWith || + m_operator == nsMsgSearchOp::EndsWith; + // init result to what we want if we don't find the header at all + bool result = !matchExpected; + + nsCString dbHdrValue; + msg->GetStringProperty(m_arbitraryHeader.get(), getter_Copies(dbHdrValue)); + if (!dbHdrValue.IsEmpty()) + // match value with the other info. + return MatchRfc2047String(dbHdrValue, charset, charsetOverride, pResult); + + nsMsgBodyHandler * bodyHandler = + new nsMsgBodyHandler (scope, length, msg, db, headers, headersSize, + ForFiltering); + NS_ENSURE_TRUE(bodyHandler, NS_ERROR_OUT_OF_MEMORY); + + bodyHandler->SetStripHeaders (false); + + nsCString headerFullValue; // contains matched header value accumulated over multiple lines. + nsAutoCString buf; + nsAutoCString curMsgHeader; + bool searchingHeaders = true; + + // We will allow accumulation of received headers; + bool isReceivedHeader = m_arbitraryHeader.EqualsLiteral("received"); + + while (searchingHeaders) + { + nsCString charsetIgnored; + if (bodyHandler->GetNextLine(buf, charsetIgnored) < 0 || EMPTY_MESSAGE_LINE(buf)) + searchingHeaders = false; + bool isContinuationHeader = searchingHeaders ? NS_IsAsciiWhitespace(buf.CharAt(0)) + : false; + + // We try to match the header from the last time through the loop, which should now + // have accumulated over possible multiple lines. For all headers except received, + // we process a single accumulation, but process accumulated received at the end. + if (!searchingHeaders || (!isContinuationHeader && + (!headerFullValue.IsEmpty() && !isReceivedHeader))) + { + // Make sure buf has info besides just the header. + // Otherwise, it's either an empty header, or header not found. + if (!headerFullValue.IsEmpty()) + { + bool stringMatches; + // match value with the other info. + rv = MatchRfc2047String(headerFullValue, charset, charsetOverride, &stringMatches); + if (matchExpected == stringMatches) // if we found a match + { + searchingHeaders = false; // then stop examining the headers + result = stringMatches; + } + } + break; + } + + char * buf_end = (char *) (buf.get() + buf.Length()); + int headerLength = m_arbitraryHeader.Length(); + + // If the line starts with whitespace, then we use the current header. + if (!isContinuationHeader) + { + // here we start a new header + uint32_t colonPos = buf.FindChar(':'); + curMsgHeader = StringHead(buf, colonPos); + } + + if (curMsgHeader.Equals(m_arbitraryHeader, nsCaseInsensitiveCStringComparator())) + { + // process the value + // value occurs after the header name or whitespace continuation char. + const char * headerValue = buf.get() + (isContinuationHeader ? 1 : headerLength); + if (headerValue < buf_end && headerValue[0] == ':') // + 1 to account for the colon which is MANDATORY + headerValue++; + + // strip leading white space + while (headerValue < buf_end && isspace(*headerValue)) + headerValue++; // advance to next character + + // strip trailing white space + char * end = buf_end - 1; + while (end > headerValue && isspace(*end)) // while we haven't gone back past the start and we are white space.... + { + *end = '\0'; // eat up the white space + end--; // move back and examine the previous character.... + } + + // any continuation whitespace is converted to a single space. This includes both a continuation line, or a + // second value of the same header (eg the received header) + if (!headerFullValue.IsEmpty()) + headerFullValue.AppendLiteral(" "); + headerFullValue.Append(nsDependentCString(headerValue)); + } + } + delete bodyHandler; + *pResult = result; + return rv; +} + +NS_IMETHODIMP nsMsgSearchTerm::MatchHdrProperty(nsIMsgDBHdr *aHdr, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aHdr); + + nsCString dbHdrValue; + aHdr->GetStringProperty(m_hdrProperty.get(), getter_Copies(dbHdrValue)); + return MatchString(dbHdrValue, nullptr, aResult); +} + +NS_IMETHODIMP nsMsgSearchTerm::MatchFolderFlag(nsIMsgDBHdr *aMsgToMatch, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aMsgToMatch); + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsIMsgFolder> msgFolder; + nsresult rv = aMsgToMatch->GetFolder(getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t folderFlags; + msgFolder->GetFlags(&folderFlags); + return MatchStatus(folderFlags, aResult); +} + +NS_IMETHODIMP nsMsgSearchTerm::MatchUint32HdrProperty(nsIMsgDBHdr *aHdr, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aHdr); + + nsresult rv = NS_OK; + uint32_t dbHdrValue; + aHdr->GetUint32Property(m_hdrProperty.get(), &dbHdrValue); + + bool result = false; + switch (m_operator) + { + case nsMsgSearchOp::IsGreaterThan: + if (dbHdrValue > m_value.u.msgStatus) + result = true; + break; + case nsMsgSearchOp::IsLessThan: + if (dbHdrValue < m_value.u.msgStatus) + result = true; + break; + case nsMsgSearchOp::Is: + if (dbHdrValue == m_value.u.msgStatus) + result = true; + break; + case nsMsgSearchOp::Isnt: + if (dbHdrValue != m_value.u.msgStatus) + result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for uint"); + } + *aResult = result; + return rv; +} + +nsresult nsMsgSearchTerm::MatchBody (nsIMsgSearchScopeTerm *scope, uint64_t offset, uint32_t length /*in lines*/, const char *folderCharset, + nsIMsgDBHdr *msg, nsIMsgDatabase* db, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + + bool result = false; + *pResult = false; + + // Small hack so we don't look all through a message when someone has + // specified "BODY IS foo". ### Since length is in lines, this is not quite right. + if ((length > 0) && (m_operator == nsMsgSearchOp::Is || m_operator == nsMsgSearchOp::Isnt)) + length = PL_strlen (m_value.string); + + nsMsgBodyHandler * bodyHan = new nsMsgBodyHandler (scope, length, msg, db); + if (!bodyHan) + return NS_ERROR_OUT_OF_MEMORY; + + nsAutoCString buf; + bool endOfFile = false; // if retValue == 0, we've hit the end of the file + uint32_t lines = 0; + + // Change the sense of the loop so we don't bail out prematurely + // on negative terms. i.e. opDoesntContain must look at all lines + bool boolContinueLoop; + GetMatchAllBeforeDeciding(&boolContinueLoop); + result = boolContinueLoop; + + // If there's a '=' in the search term, then we're not going to do + // quoted printable decoding. Otherwise we assume everything is + // quoted printable. Obviously everything isn't quoted printable, but + // since we don't have a MIME parser handy, and we want to err on the + // side of too many hits rather than not enough, we'll assume in that + // general direction. Blech. ### FIX ME + // bug fix #314637: for stateful charsets like ISO-2022-JP, we don't + // want to decode quoted printable since it contains '='. + bool isQuotedPrintable = !nsMsgI18Nstateful_charset(folderCharset) && + (PL_strchr (m_value.string, '=') == nullptr); + + nsCString compare; + nsCString charset; + while (!endOfFile && result == boolContinueLoop) + { + if (bodyHan->GetNextLine(buf, charset) >= 0) + { + bool softLineBreak = false; + // Do in-place decoding of quoted printable + if (isQuotedPrintable) + { + softLineBreak = StringEndsWith(buf, NS_LITERAL_CSTRING("=")); + MsgStripQuotedPrintable ((unsigned char*)buf.get()); + // in case the string shrunk, reset the length. If soft line break, + // chop off the last char as well. + size_t bufLength = strlen(buf.get()); + if ((bufLength > 0) && softLineBreak) + --bufLength; + buf.SetLength(bufLength); + } + compare.Append(buf); + // If this line ends with a soft line break, loop around + // and get the next line before looking for the search string. + // This assumes the message can't end on a QP soft-line break. + // That seems like a pretty safe assumption. + if (softLineBreak) + continue; + if (!compare.IsEmpty()) + { + char startChar = (char) compare.CharAt(0); + if (startChar != '\r' && startChar != '\n') + { + rv = MatchString(compare, + charset.IsEmpty() ? folderCharset : charset.get(), + &result); + lines++; + } + compare.Truncate(); + } + } + else + endOfFile = true; + } +#ifdef DO_I18N + if(conv) + INTL_DestroyCharCodeConverter(conv); +#endif + delete bodyHan; + *pResult = result; + return rv; +} + +nsresult nsMsgSearchTerm::InitializeAddressBook() +{ + // the search attribute value has the URI for the address book we need to load. + // we need both the database and the directory. + nsresult rv = NS_OK; + + if (mDirectory) + { + nsCString uri; + rv = mDirectory->GetURI(uri); + NS_ENSURE_SUCCESS(rv, rv); + + if (!uri.Equals(m_value.string)) + // clear out the directory....we are no longer pointing to the right one + mDirectory = nullptr; + } + if (!mDirectory) + { + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = abManager->GetDirectory(nsDependentCString(m_value.string), getter_AddRefs(mDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult nsMsgSearchTerm::MatchInAddressBook(const nsAString &aAddress, + bool *pResult) +{ + nsresult rv = InitializeAddressBook(); + *pResult = false; + + // Some junkmails have empty From: fields. + if (aAddress.IsEmpty()) + return rv; + + if (mDirectory) + { + nsCOMPtr<nsIAbCard> cardForAddress = nullptr; + rv = mDirectory->CardForEmailAddress(NS_ConvertUTF16toUTF8(aAddress), + getter_AddRefs(cardForAddress)); + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) + return rv; + switch(m_operator) + { + case nsMsgSearchOp::IsInAB: + if (cardForAddress) + *pResult = true; + break; + case nsMsgSearchOp::IsntInAB: + if (!cardForAddress) + *pResult = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for address book"); + } + } + + return rv; +} + +// *pResult is false when strings don't match, true if they do. +nsresult nsMsgSearchTerm::MatchRfc2047String(const nsACString &rfc2047string, + const char *charset, + bool charsetOverride, + bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + nsCOMPtr<nsIMimeConverter> mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID); + nsAutoString stringToMatch; + nsresult rv = mimeConverter->DecodeMimeHeader( + PromiseFlatCString(rfc2047string).get(), charset, charsetOverride, false, + stringToMatch); + NS_ENSURE_SUCCESS(rv, rv); + if (m_operator == nsMsgSearchOp::IsInAB || + m_operator == nsMsgSearchOp::IsntInAB) + return MatchInAddressBook(stringToMatch, pResult); + + return MatchString(stringToMatch, pResult); +} + +// *pResult is false when strings don't match, true if they do. +nsresult nsMsgSearchTerm::MatchString(const nsACString &stringToMatch, + const char *charset, + bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + bool result = false; + + nsresult rv = NS_OK; + + // Save some performance for opIsEmpty / opIsntEmpty + if (nsMsgSearchOp::IsEmpty == m_operator) + { + if (stringToMatch.IsEmpty()) + result = true; + } + else if (nsMsgSearchOp::IsntEmpty == m_operator) + { + if (!stringToMatch.IsEmpty()) + result = true; + } + else + { + nsAutoString utf16StrToMatch; + if (charset != nullptr) + { + ConvertToUnicode(charset, nsCString(stringToMatch), utf16StrToMatch); + } + else { + NS_ASSERTION(MsgIsUTF8(stringToMatch), "stringToMatch is not UTF-8"); + CopyUTF8toUTF16(stringToMatch, utf16StrToMatch); + } + rv = MatchString(utf16StrToMatch, &result); + } + + *pResult = result; + return rv; +} + +// *pResult is false when strings don't match, true if they do. +nsresult nsMsgSearchTerm::MatchString(const nsAString &utf16StrToMatch, + bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + bool result = false; + + nsresult rv = NS_OK; + auto needle = m_value.utf16String; + + switch (m_operator) + { + case nsMsgSearchOp::Contains: + if (CaseInsensitiveFindInReadable(needle, utf16StrToMatch)) + result = true; + break; + case nsMsgSearchOp::DoesntContain: + if (!CaseInsensitiveFindInReadable(needle, utf16StrToMatch)) + result = true; + break; + case nsMsgSearchOp::Is: + if(needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator())) + result = true; + break; + case nsMsgSearchOp::Isnt: + if(!needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator())) + result = true; + break; + case nsMsgSearchOp::IsEmpty: + if (utf16StrToMatch.IsEmpty()) + result = true; + break; + case nsMsgSearchOp::IsntEmpty: + if (!utf16StrToMatch.IsEmpty()) + result = true; + break; + case nsMsgSearchOp::BeginsWith: + if (StringBeginsWith(utf16StrToMatch, needle, + nsCaseInsensitiveStringComparator())) + result = true; + break; + case nsMsgSearchOp::EndsWith: + if (StringEndsWith(utf16StrToMatch, needle, + nsCaseInsensitiveStringComparator())) + result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for matching search results"); + } + + *pResult = result; + return rv; +} + +NS_IMETHODIMP nsMsgSearchTerm::GetMatchAllBeforeDeciding (bool *aResult) +{ + *aResult = (m_operator == nsMsgSearchOp::DoesntContain || m_operator == nsMsgSearchOp::Isnt); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchTerm::MatchRfc822String(const nsACString &string, + const char *charset, + bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + *pResult = false; + bool result; + + // Change the sense of the loop so we don't bail out prematurely + // on negative terms. i.e. opDoesntContain must look at all recipients + bool boolContinueLoop; + GetMatchAllBeforeDeciding(&boolContinueLoop); + result = boolContinueLoop; + + // If the operator is Contains, then we can cheat and avoid having to parse + // addresses. This does open up potential spurious matches for punctuation + // (e.g., ; or <), but the likelihood of users intending to search for these + // and also being able to match them is rather low. This optimization is not + // applicable to any other search type. + if (m_operator == nsMsgSearchOp::Contains) + return MatchRfc2047String(string, charset, false, pResult); + + nsTArray<nsString> names, addresses; + ExtractAllAddresses(EncodedHeader(string, charset), names, addresses); + uint32_t count = names.Length(); + + nsresult rv = NS_OK; + for (uint32_t i = 0; i < count && result == boolContinueLoop; i++) + { + if ( m_operator == nsMsgSearchOp::IsInAB || + m_operator == nsMsgSearchOp::IsntInAB) + { + rv = MatchInAddressBook(addresses[i], &result); + } + else + { + rv = MatchString(names[i], &result); + if (boolContinueLoop == result) + rv = MatchString(addresses[i], &result); + } + } + *pResult = result; + return rv; +} + + +nsresult nsMsgSearchTerm::GetLocalTimes (PRTime a, PRTime b, PRExplodedTime &aExploded, PRExplodedTime &bExploded) +{ + PR_ExplodeTime(a, PR_LocalTimeParameters, &aExploded); + PR_ExplodeTime(b, PR_LocalTimeParameters, &bExploded); + return NS_OK; +} + + +nsresult nsMsgSearchTerm::MatchDate (PRTime dateToMatch, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool result = false; + + PRExplodedTime tmToMatch, tmThis; + if (NS_SUCCEEDED(GetLocalTimes(dateToMatch, m_value.u.date, tmToMatch, tmThis))) + { + switch (m_operator) + { + case nsMsgSearchOp::IsBefore: + if (tmToMatch.tm_year < tmThis.tm_year || + (tmToMatch.tm_year == tmThis.tm_year && + tmToMatch.tm_yday < tmThis.tm_yday)) + result = true; + break; + case nsMsgSearchOp::IsAfter: + if (tmToMatch.tm_year > tmThis.tm_year || + (tmToMatch.tm_year == tmThis.tm_year && + tmToMatch.tm_yday > tmThis.tm_yday)) + result = true; + break; + case nsMsgSearchOp::Is: + if (tmThis.tm_year == tmToMatch.tm_year && + tmThis.tm_month == tmToMatch.tm_month && + tmThis.tm_mday == tmToMatch.tm_mday) + result = true; + break; + case nsMsgSearchOp::Isnt: + if (tmThis.tm_year != tmToMatch.tm_year || + tmThis.tm_month != tmToMatch.tm_month || + tmThis.tm_mday != tmToMatch.tm_mday) + result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for dates"); + } + } + *pResult = result; + return rv; +} + + +nsresult nsMsgSearchTerm::MatchAge (PRTime msgDate, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + bool result = false; + nsresult rv = NS_OK; + + PRTime now = PR_Now(); + PRTime cutOffDay = now - m_value.u.age * PR_USEC_PER_DAY; + + bool cutOffDayInTheFuture = m_value.u.age < 0; + + // So now cutOffDay is the PRTime cut-off point. + // Any msg with a time less than that will be past the age. + + switch (m_operator) + { + case nsMsgSearchOp::IsGreaterThan: // is older than, or more in the future + if ((!cutOffDayInTheFuture && msgDate < cutOffDay) || + (cutOffDayInTheFuture && msgDate > cutOffDay)) + result = true; + break; + case nsMsgSearchOp::IsLessThan: // is younger than, or less in the future + if ((!cutOffDayInTheFuture && msgDate > cutOffDay) || + (cutOffDayInTheFuture && msgDate < cutOffDay)) + result = true; + break; + case nsMsgSearchOp::Is: + PRExplodedTime msgDateExploded; + PRExplodedTime cutOffDayExploded; + if (NS_SUCCEEDED(GetLocalTimes(msgDate, cutOffDay, msgDateExploded, cutOffDayExploded))) + { + if ((msgDateExploded.tm_mday == cutOffDayExploded.tm_mday) && + (msgDateExploded.tm_month == cutOffDayExploded.tm_month) && + (msgDateExploded.tm_year == cutOffDayExploded.tm_year)) + result = true; + } + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for msg age"); + } + *pResult = result; + return rv; +} + + +nsresult nsMsgSearchTerm::MatchSize (uint32_t sizeToMatch, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool result = false; + // We reduce the sizeToMatch rather than supplied size + // as then we can do an exact match on the displayed value + // which will be less confusing to the user. + uint32_t sizeToMatchKB = sizeToMatch; + + if (sizeToMatchKB < 1024) + sizeToMatchKB = 1024; + + sizeToMatchKB /= 1024; + + switch (m_operator) + { + case nsMsgSearchOp::IsGreaterThan: + if (sizeToMatchKB > m_value.u.size) + result = true; + break; + case nsMsgSearchOp::IsLessThan: + if (sizeToMatchKB < m_value.u.size) + result = true; + break; + case nsMsgSearchOp::Is: + if (sizeToMatchKB == m_value.u.size) + result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for size to match"); + } + *pResult = result; + return rv; +} + +nsresult nsMsgSearchTerm::MatchJunkStatus(const char *aJunkScore, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + if (m_operator == nsMsgSearchOp::IsEmpty) + { + *pResult = !(aJunkScore && *aJunkScore); + return NS_OK; + } + if (m_operator == nsMsgSearchOp::IsntEmpty) + { + *pResult = (aJunkScore && *aJunkScore); + return NS_OK; + } + + nsMsgJunkStatus junkStatus; + if (aJunkScore && *aJunkScore) { + junkStatus = (atoi(aJunkScore) == nsIJunkMailPlugin::IS_SPAM_SCORE) ? + nsIJunkMailPlugin::JUNK : + nsIJunkMailPlugin::GOOD; + + } + else { + // the in UI, we only show "junk" or "not junk" + // unknown, or nsIJunkMailPlugin::UNCLASSIFIED is shown as not junk + // so for the search to work as expected, treat unknown as not junk + junkStatus = nsIJunkMailPlugin::GOOD; + } + + nsresult rv = NS_OK; + bool matches = (junkStatus == m_value.u.junkStatus); + + switch (m_operator) + { + case nsMsgSearchOp::Is: + break; + case nsMsgSearchOp::Isnt: + matches = !matches; + break; + default: + rv = NS_ERROR_FAILURE; + matches = false; + NS_ERROR("invalid compare op for junk status"); + } + + *pResult = matches; + return rv; +} + +nsresult nsMsgSearchTerm::MatchJunkScoreOrigin(const char *aJunkScoreOrigin, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + bool matches = false; + nsresult rv = NS_OK; + + switch (m_operator) + { + case nsMsgSearchOp::Is: + matches = aJunkScoreOrigin && !strcmp(aJunkScoreOrigin, m_value.string); + break; + case nsMsgSearchOp::Isnt: + matches = !aJunkScoreOrigin || strcmp(aJunkScoreOrigin, m_value.string); + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for junk score origin"); + } + + *pResult = matches; + return rv; +} + +nsresult nsMsgSearchTerm::MatchJunkPercent(uint32_t aJunkPercent, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool result = false; + switch (m_operator) + { + case nsMsgSearchOp::IsGreaterThan: + if (aJunkPercent > m_value.u.junkPercent) + result = true; + break; + case nsMsgSearchOp::IsLessThan: + if (aJunkPercent < m_value.u.junkPercent) + result = true; + break; + case nsMsgSearchOp::Is: + if (aJunkPercent == m_value.u.junkPercent) + result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for junk percent"); + } + *pResult = result; + return rv; +} + + +nsresult nsMsgSearchTerm::MatchLabel(nsMsgLabelValue aLabelValue, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool result = false; + switch (m_operator) + { + case nsMsgSearchOp::Is: + if (m_value.u.label == aLabelValue) + result = true; + break; + case nsMsgSearchOp::Isnt: + if (m_value.u.label != aLabelValue) + result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for label value"); + } + + *pResult = result; + return rv; +} + +// MatchStatus () is not only used for nsMsgMessageFlags but also for +// nsMsgFolderFlags (both being 'unsigned long') +nsresult nsMsgSearchTerm::MatchStatus(uint32_t statusToMatch, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool matches = (statusToMatch & m_value.u.msgStatus); + +// nsMsgSearchOp::Is and nsMsgSearchOp::Isnt are intentionally used as +// Contains and DoesntContain respectively, for legacy reasons. + switch (m_operator) + { + case nsMsgSearchOp::Is: + break; + case nsMsgSearchOp::Isnt: + matches = !matches; + break; + default: + rv = NS_ERROR_FAILURE; + matches = false; + NS_ERROR("invalid compare op for msg status"); + } + + *pResult = matches; + return rv; +} + +/* + * MatchKeyword Logic table (*pResult: + is true, - is false) + * + * # Valid Tokens IsEmpty IsntEmpty Contains DoesntContain Is Isnt + * 0 + - - + - + + * Term found? N Y N Y N Y N Y + * 1 - + - + + - - + + - + * >1 - + - + + - - - + + + */ +// look up nsMsgSearchTerm::m_value in space-delimited keywordList +nsresult nsMsgSearchTerm::MatchKeyword(const nsACString& keywordList, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + bool matches = false; + + // special-case empty for performance reasons + if (keywordList.IsEmpty()) + { + *pResult = m_operator != nsMsgSearchOp::Contains && + m_operator != nsMsgSearchOp::Is && + m_operator != nsMsgSearchOp::IsntEmpty; + return NS_OK; + } + + // check if we can skip expensive valid keywordList test + if (m_operator == nsMsgSearchOp::DoesntContain || + m_operator == nsMsgSearchOp::Contains) + { + nsCString keywordString(keywordList); + const uint32_t kKeywordLen = PL_strlen(m_value.string); + const char* matchStart = PL_strstr(keywordString.get(), m_value.string); + while (matchStart) + { + // For a real match, matchStart must be the start of the keywordList or + // preceded by a space and matchEnd must point to a \0 or space. + const char* matchEnd = matchStart + kKeywordLen; + if ((matchStart == keywordString.get() || matchStart[-1] == ' ') && + (!*matchEnd || *matchEnd == ' ')) + { + // found the keyword + *pResult = m_operator == nsMsgSearchOp::Contains; + return NS_OK; + } + // no match yet, so search on + matchStart = PL_strstr(matchEnd, m_value.string); + } + // keyword not found + *pResult = m_operator == nsMsgSearchOp::DoesntContain; + return NS_OK; + } + + // Only accept valid keys in tokens. + nsresult rv = NS_OK; + nsTArray<nsCString> keywordArray; + ParseString(keywordList, ' ', keywordArray); + nsCOMPtr<nsIMsgTagService> tagService(do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Loop through tokens in keywords + uint32_t count = keywordArray.Length(); + for (uint32_t i = 0; i < count; i++) + { + // is this token a valid tag? Otherwise ignore it + bool isValid; + rv = tagService->IsValidKey(keywordArray[i], &isValid); + NS_ENSURE_SUCCESS(rv, rv); + + if (isValid) + { + // IsEmpty fails on any valid token + if (m_operator == nsMsgSearchOp::IsEmpty) + { + *pResult = false; + return rv; + } + + // IsntEmpty succeeds on any valid token + if (m_operator == nsMsgSearchOp::IsntEmpty) + { + *pResult = true; + return rv; + } + + // Does this valid tag key match our search term? + matches = keywordArray[i].Equals(m_value.string); + + // Is or Isn't partly determined on a single unmatched token + if (!matches) + { + if (m_operator == nsMsgSearchOp::Is) + { + *pResult = false; + return rv; + } + if (m_operator == nsMsgSearchOp::Isnt) + { + *pResult = true; + return rv; + } + } + } + } + + if (m_operator == nsMsgSearchOp::Is) + { + *pResult = matches; + return NS_OK; + } + + if (m_operator == nsMsgSearchOp::Isnt) + { + *pResult = !matches; + return NS_OK; + } + + if (m_operator == nsMsgSearchOp::IsEmpty) + { + *pResult = true; + return NS_OK; + } + + if (m_operator == nsMsgSearchOp::IsntEmpty) + { + *pResult = false; + return NS_OK; + } + + // no valid match operator found + *pResult = false; + NS_ERROR("invalid compare op for msg status"); + return NS_ERROR_FAILURE; +} + +nsresult +nsMsgSearchTerm::MatchPriority (nsMsgPriorityValue priorityToMatch, + bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool result = false; + + // Use this ugly little hack to get around the fact that enums don't have + // integer compare operators + int p1 = (priorityToMatch == nsMsgPriority::none) ? (int) nsMsgPriority::normal : (int) priorityToMatch; + int p2 = (int) m_value.u.priority; + + switch (m_operator) + { + case nsMsgSearchOp::IsHigherThan: + if (p1 > p2) + result = true; + break; + case nsMsgSearchOp::IsLowerThan: + if (p1 < p2) + result = true; + break; + case nsMsgSearchOp::Is: + if (p1 == p2) + result = true; + break; + case nsMsgSearchOp::Isnt: + if (p1 != p2) + result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for priority"); + } + *pResult = result; + return rv; +} + +// match a custom search term +NS_IMETHODIMP nsMsgSearchTerm::MatchCustom(nsIMsgDBHdr* aHdr, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv; + nsCOMPtr<nsIMsgFilterService> filterService = + do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgSearchCustomTerm> customTerm; + rv = filterService->GetCustomTerm(m_customId, getter_AddRefs(customTerm)); + NS_ENSURE_SUCCESS(rv, rv); + + if (customTerm) + return customTerm->Match(aHdr, nsDependentCString(m_value.string), + m_operator, pResult); + *pResult = false; // default to no match if term is missing + return NS_ERROR_FAILURE; // missing custom term +} + +// set the id of a custom search term +NS_IMETHODIMP nsMsgSearchTerm::SetCustomId(const nsACString &aId) +{ + m_customId = aId; + return NS_OK; +} + +// get the id of a custom search term +NS_IMETHODIMP nsMsgSearchTerm::GetCustomId(nsACString &aResult) +{ + aResult = m_customId; + return NS_OK; +} + + +NS_IMPL_GETSET(nsMsgSearchTerm, Attrib, nsMsgSearchAttribValue, m_attribute) +NS_IMPL_GETSET(nsMsgSearchTerm, Op, nsMsgSearchOpValue, m_operator) +NS_IMPL_GETSET(nsMsgSearchTerm, MatchAll, bool, m_matchAll) + +NS_IMETHODIMP +nsMsgSearchTerm::GetValue(nsIMsgSearchValue **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = new nsMsgSearchValueImpl(&m_value); + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::SetValue(nsIMsgSearchValue* aValue) +{ + nsMsgResultElement::AssignValues (aValue, &m_value); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::GetBooleanAnd(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = (m_booleanOp == nsMsgSearchBooleanOp::BooleanAND); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::SetBooleanAnd(bool aValue) +{ + if (aValue) + m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanAND); + else + m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanOR); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::GetArbitraryHeader(nsACString &aResult) +{ + aResult = m_arbitraryHeader; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::SetArbitraryHeader(const nsACString &aValue) +{ + m_arbitraryHeader = aValue; + ToLowerCaseExceptSpecials(m_arbitraryHeader); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::GetHdrProperty(nsACString &aResult) +{ + aResult = m_hdrProperty; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::SetHdrProperty(const nsACString &aValue) +{ + m_hdrProperty = aValue; + ToLowerCaseExceptSpecials(m_hdrProperty); + return NS_OK; +} + +NS_IMPL_GETSET(nsMsgSearchTerm, BeginsGrouping, bool, mBeginsGrouping) +NS_IMPL_GETSET(nsMsgSearchTerm, EndsGrouping, bool, mEndsGrouping) + +// +// Certain possible standard values of a message database row also sometimes +// appear as header values. To prevent a naming collision, we use all +// lower case for the standard headers, and first capital when those +// same strings are requested as arbitrary headers. This routine is used +// when setting arbitrary headers. +// +void nsMsgSearchTerm::ToLowerCaseExceptSpecials(nsACString &aValue) +{ + if (aValue.LowerCaseEqualsLiteral("sender")) + aValue.Assign(NS_LITERAL_CSTRING("Sender")); + else if (aValue.LowerCaseEqualsLiteral("date")) + aValue.Assign(NS_LITERAL_CSTRING("Date")); + else if (aValue.LowerCaseEqualsLiteral("status")) + aValue.Assign(NS_LITERAL_CSTRING("Status")); + else + ToLowerCase(aValue); +} + + +//----------------------------------------------------------------------------- +// nsMsgSearchScopeTerm implementation +//----------------------------------------------------------------------------- +nsMsgSearchScopeTerm::nsMsgSearchScopeTerm (nsIMsgSearchSession *session, + nsMsgSearchScopeValue attribute, + nsIMsgFolder *folder) +{ + m_attribute = attribute; + m_folder = folder; + m_searchServer = true; + m_searchSession = do_GetWeakReference(session); +} + +nsMsgSearchScopeTerm::nsMsgSearchScopeTerm () +{ + m_searchServer = true; +} + +nsMsgSearchScopeTerm::~nsMsgSearchScopeTerm () +{ + if (m_inputStream) + m_inputStream->Close(); + m_inputStream = nullptr; +} + +NS_IMPL_ISUPPORTS(nsMsgSearchScopeTerm, nsIMsgSearchScopeTerm) + +NS_IMETHODIMP +nsMsgSearchScopeTerm::GetFolder(nsIMsgFolder **aResult) +{ + NS_IF_ADDREF(*aResult = m_folder); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchScopeTerm::GetSearchSession(nsIMsgSearchSession** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + nsCOMPtr<nsIMsgSearchSession> searchSession = do_QueryReferent (m_searchSession); + NS_IF_ADDREF(*aResult = searchSession); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchScopeTerm::GetInputStream(nsIMsgDBHdr *aMsgHdr, + nsIInputStream **aInputStream) +{ + NS_ENSURE_ARG_POINTER(aInputStream); + NS_ENSURE_ARG_POINTER(aMsgHdr); + NS_ENSURE_TRUE(m_folder, NS_ERROR_NULL_POINTER); + bool reusable; + nsresult rv = m_folder->GetMsgInputStream(aMsgHdr, &reusable, + getter_AddRefs(m_inputStream)); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*aInputStream = m_inputStream); + return rv; +} + +NS_IMETHODIMP nsMsgSearchScopeTerm::CloseInputStream() +{ + if (m_inputStream) +{ + m_inputStream->Close(); + m_inputStream = nullptr; + } + return NS_OK; +} + +nsresult nsMsgSearchScopeTerm::TimeSlice (bool *aDone) +{ + return m_adapter->Search(aDone); +} + +nsresult nsMsgSearchScopeTerm::InitializeAdapter (nsISupportsArray *termList) +{ + if (m_adapter) + return NS_OK; + + nsresult rv = NS_OK; + + switch (m_attribute) + { + case nsMsgSearchScope::onlineMail: + m_adapter = new nsMsgSearchOnlineMail (this, termList); + break; + case nsMsgSearchScope::offlineMail: + case nsMsgSearchScope::onlineManual: + m_adapter = new nsMsgSearchOfflineMail (this, termList); + break; + case nsMsgSearchScope::newsEx: + NS_ASSERTION(false, "not supporting newsEx yet"); + break; + case nsMsgSearchScope::news: + m_adapter = new nsMsgSearchNews (this, termList); + break; + case nsMsgSearchScope::allSearchableGroups: + NS_ASSERTION(false, "not supporting allSearchableGroups yet"); + break; + case nsMsgSearchScope::LDAP: + NS_ASSERTION(false, "not supporting LDAP yet"); + break; + case nsMsgSearchScope::localNews: + case nsMsgSearchScope::localNewsJunk: + case nsMsgSearchScope::localNewsBody: + case nsMsgSearchScope::localNewsJunkBody: + m_adapter = new nsMsgSearchOfflineNews (this, termList); + break; + default: + NS_ASSERTION(false, "invalid scope"); + rv = NS_ERROR_FAILURE; + } + + if (m_adapter) + rv = m_adapter->ValidateTerms (); + + return rv; +} + + +char *nsMsgSearchScopeTerm::GetStatusBarName () +{ + return nullptr; +} + + +//----------------------------------------------------------------------------- +// nsMsgResultElement implementation +//----------------------------------------------------------------------------- + + +nsMsgResultElement::nsMsgResultElement(nsIMsgSearchAdapter *adapter) +{ + m_adapter = adapter; +} + + +nsMsgResultElement::~nsMsgResultElement () +{ +} + + +nsresult nsMsgResultElement::AddValue (nsIMsgSearchValue *value) +{ + m_valueList.AppendElement(value); + return NS_OK; +} + +nsresult nsMsgResultElement::AddValue (nsMsgSearchValue *value) +{ + nsMsgSearchValueImpl* valueImpl = new nsMsgSearchValueImpl(value); + delete value; // we keep the nsIMsgSearchValue, not + // the nsMsgSearchValue + return AddValue(valueImpl); +} + + +nsresult nsMsgResultElement::AssignValues (nsIMsgSearchValue *src, nsMsgSearchValue *dst) +{ + NS_ENSURE_ARG_POINTER(src); + NS_ENSURE_ARG_POINTER(dst); + // Yes, this could be an operator overload, but nsMsgSearchValue is totally public, so I'd + // have to define a derived class with nothing by operator=, and that seems like a bit much + nsresult rv = NS_OK; + src->GetAttrib(&dst->attribute); + switch (dst->attribute) + { + case nsMsgSearchAttrib::Priority: + rv = src->GetPriority(&dst->u.priority); + break; + case nsMsgSearchAttrib::Date: + rv = src->GetDate(&dst->u.date); + break; + case nsMsgSearchAttrib::HasAttachmentStatus: + case nsMsgSearchAttrib::MsgStatus: + case nsMsgSearchAttrib::FolderFlag: + case nsMsgSearchAttrib::Uint32HdrProperty: + rv = src->GetStatus(&dst->u.msgStatus); + break; + case nsMsgSearchAttrib::MessageKey: + rv = src->GetMsgKey(&dst->u.key); + break; + case nsMsgSearchAttrib::AgeInDays: + rv = src->GetAge(&dst->u.age); + break; + case nsMsgSearchAttrib::Label: + rv = src->GetLabel(&dst->u.label); + break; + case nsMsgSearchAttrib::JunkStatus: + rv = src->GetJunkStatus(&dst->u.junkStatus); + break; + case nsMsgSearchAttrib::JunkPercent: + rv = src->GetJunkPercent(&dst->u.junkPercent); + break; + case nsMsgSearchAttrib::Size: + rv = src->GetSize(&dst->u.size); + break; + default: + if (dst->attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes) + { + NS_ASSERTION(IS_STRING_ATTRIBUTE(dst->attribute), "assigning non-string result"); + nsString unicodeString; + rv = src->GetStr(unicodeString); + dst->string = ToNewUTF8String(unicodeString); + dst->utf16String = unicodeString; + } + else + rv = NS_ERROR_INVALID_ARG; + } + return rv; +} + + +nsresult nsMsgResultElement::GetValue (nsMsgSearchAttribValue attrib, + nsMsgSearchValue **outValue) const +{ + nsresult rv = NS_OK; + *outValue = NULL; + + for (uint32_t i = 0; i < m_valueList.Length() && NS_FAILED(rv); i++) + { + nsMsgSearchAttribValue valueAttribute; + m_valueList[i]->GetAttrib(&valueAttribute); + if (attrib == valueAttribute) + { + *outValue = new nsMsgSearchValue; + if (*outValue) + { + rv = AssignValues(m_valueList[i], *outValue); + // Now this is really strange! What is this assignment doing here? + rv = NS_OK; + } + else + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + return rv; +} + +nsresult nsMsgResultElement::GetPrettyName (nsMsgSearchValue **value) +{ + return GetValue (nsMsgSearchAttrib::Location, value); +} + +nsresult nsMsgResultElement::Open (void *window) +{ + return NS_ERROR_NULL_POINTER; +} + + diff --git a/mailnews/base/search/src/nsMsgSearchValue.cpp b/mailnews/base/search/src/nsMsgSearchValue.cpp new file mode 100644 index 000000000..7685c4a73 --- /dev/null +++ b/mailnews/base/search/src/nsMsgSearchValue.cpp @@ -0,0 +1,117 @@ +/* -*- 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/. */ + +#include "MailNewsTypes.h" +#include "nsMsgSearchValue.h" +#include "nsIMsgFolder.h" +#include "nsMsgUtils.h" +#include "nsStringGlue.h" + +nsMsgSearchValueImpl::nsMsgSearchValueImpl(nsMsgSearchValue *aInitialValue) +{ + mValue = *aInitialValue; + if (IS_STRING_ATTRIBUTE(aInitialValue->attribute) && aInitialValue->string) + { + mValue.string = NS_strdup(aInitialValue->string); + CopyUTF8toUTF16(mValue.string, mValue.utf16String); + } + else + mValue.string = 0; +} + +nsMsgSearchValueImpl::~nsMsgSearchValueImpl() +{ + if (IS_STRING_ATTRIBUTE(mValue.attribute)) + NS_Free(mValue.string); +} + +NS_IMPL_ISUPPORTS(nsMsgSearchValueImpl, nsIMsgSearchValue) + +NS_IMPL_GETSET(nsMsgSearchValueImpl, Priority, nsMsgPriorityValue, mValue.u.priority) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Status, uint32_t, mValue.u.msgStatus) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Size, uint32_t, mValue.u.size) +NS_IMPL_GETSET(nsMsgSearchValueImpl, MsgKey, nsMsgKey, mValue.u.key) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Age, int32_t, mValue.u.age) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Date, PRTime, mValue.u.date) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Attrib, nsMsgSearchAttribValue, mValue.attribute) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Label, nsMsgLabelValue, mValue.u.label) +NS_IMPL_GETSET(nsMsgSearchValueImpl, JunkStatus, uint32_t, mValue.u.junkStatus) +NS_IMPL_GETSET(nsMsgSearchValueImpl, JunkPercent, uint32_t, mValue.u.junkPercent) + +NS_IMETHODIMP +nsMsgSearchValueImpl::GetFolder(nsIMsgFolder* *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(mValue.attribute == nsMsgSearchAttrib::FolderInfo, NS_ERROR_ILLEGAL_VALUE); + *aResult = mValue.u.folder; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValueImpl::SetFolder(nsIMsgFolder* aValue) +{ + NS_ENSURE_TRUE(mValue.attribute == nsMsgSearchAttrib::FolderInfo, NS_ERROR_ILLEGAL_VALUE); + mValue.u.folder = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValueImpl::GetStr(nsAString &aResult) +{ + NS_ENSURE_TRUE(IS_STRING_ATTRIBUTE(mValue.attribute), NS_ERROR_ILLEGAL_VALUE); + aResult = mValue.utf16String; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValueImpl::SetStr(const nsAString &aValue) +{ + NS_ENSURE_TRUE(IS_STRING_ATTRIBUTE(mValue.attribute), NS_ERROR_ILLEGAL_VALUE); + if (mValue.string) + NS_Free(mValue.string); + mValue.string = ToNewUTF8String(aValue); + mValue.utf16String = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValueImpl::ToString(nsAString &aResult) +{ + aResult.AssignLiteral("[nsIMsgSearchValue: "); + if (IS_STRING_ATTRIBUTE(mValue.attribute)) { + aResult.Append(mValue.utf16String); + return NS_OK; + } + + + switch (mValue.attribute) { + + case nsMsgSearchAttrib::Priority: + case nsMsgSearchAttrib::Date: + case nsMsgSearchAttrib::MsgStatus: + case nsMsgSearchAttrib::MessageKey: + case nsMsgSearchAttrib::Size: + case nsMsgSearchAttrib::AgeInDays: + case nsMsgSearchAttrib::FolderInfo: + case nsMsgSearchAttrib::Label: + case nsMsgSearchAttrib::JunkStatus: + case nsMsgSearchAttrib::JunkPercent: + { + nsAutoString tempInt; + tempInt.AppendInt(mValue.attribute); + + aResult.AppendLiteral("type="); + aResult.Append(tempInt); + } + break; + default: + NS_ERROR("Unknown search value type"); + } + + aResult.AppendLiteral("]"); + + return NS_OK; +} diff --git a/mailnews/base/search/src/nsMsgSearchValue.h b/mailnews/base/search/src/nsMsgSearchValue.h new file mode 100644 index 000000000..ef50ad1ed --- /dev/null +++ b/mailnews/base/search/src/nsMsgSearchValue.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef __nsMsgSearchValue_h +#define __nsMsgSearchValue_h + +#include "nsIMsgSearchValue.h" +#include "nsMsgSearchCore.h" + +class nsMsgSearchValueImpl : public nsIMsgSearchValue { + public: + nsMsgSearchValueImpl(nsMsgSearchValue *aInitialValue); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSEARCHVALUE + + private: + virtual ~nsMsgSearchValueImpl(); + + nsMsgSearchValue mValue; + +}; + +#endif diff --git a/mailnews/base/search/src/nsMsgTraitService.js b/mailnews/base/search/src/nsMsgTraitService.js new file mode 100644 index 000000000..eda20bbf8 --- /dev/null +++ b/mailnews/base/search/src/nsMsgTraitService.js @@ -0,0 +1,239 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +// local static variables + +var _lastIndex = 0; // the first index will be one +var _traits = {}; + +var traitsBranch = Services.prefs.getBranch("mailnews.traits."); + +function _registerTrait(aId, aIndex) +{ + var trait = {}; + trait.enabled = false; + trait.name = ""; + trait.antiId = ""; + trait.index = aIndex; + _traits[aId] = trait; + return; +} + +function nsMsgTraitService() {} + +nsMsgTraitService.prototype = +{ + // Component setup + classID: Components.ID("{A2E95F4F-DA72-4a41-9493-661AD353C00A}"), + + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.nsIMsgTraitService]), + + // nsIMsgTraitService implementation + + get lastIndex() + { + return _lastIndex; + }, + + registerTrait: function(aId) + { + if (_traits[aId]) + return 0; // meaning already registered + _registerTrait(aId, ++_lastIndex); + traitsBranch.setBoolPref("enabled." + _lastIndex, false); + traitsBranch.setCharPref("id." + _lastIndex, aId); + return _lastIndex; + }, + + unRegisterTrait: function(aId) + { + if (_traits[aId]) + { + var index = _traits[aId].index; + _traits[aId] = null; + traitsBranch.clearUserPref("id." + index); + traitsBranch.clearUserPref("enabled." + index); + traitsBranch.clearUserPref("antiId." + index); + traitsBranch.clearUserPref("name." + index); + } + return; + }, + + isRegistered: function(aId) + { + return _traits[aId] ? true : false; + }, + + setName: function(aId, aName) + { + traitsBranch.setCharPref("name." + _traits[aId].index, aName); + _traits[aId].name = aName; + }, + + getName: function(aId) + { + return _traits[aId].name; + }, + + getIndex: function(aId) + { + return _traits[aId].index; + }, + + getId: function(aIndex) + { + for (let id in _traits) + if (_traits[id].index == aIndex) + return id; + return null; + }, + + setEnabled: function(aId, aEnabled) + { + traitsBranch.setBoolPref("enabled." + _traits[aId].index, aEnabled); + _traits[aId].enabled = aEnabled; + }, + + getEnabled: function(aId) + { + return _traits[aId].enabled; + }, + + setAntiId: function(aId, aAntiId) + { + traitsBranch.setCharPref("antiId." + _traits[aId].index, aAntiId); + _traits[aId].antiId = aAntiId; + }, + + getAntiId: function(aId) + { + return _traits[aId].antiId; + }, + + getEnabledIndices: function(aCount, aProIndices, aAntiIndices) + { + let proIndices = []; + let antiIndices = []; + for (let id in _traits) + if (_traits[id].enabled) + { + proIndices.push(_traits[id].index); + antiIndices.push(_traits[_traits[id].antiId].index); + } + aCount.value = proIndices.length; + aProIndices.value = proIndices; + aAntiIndices.value = antiIndices; + return; + }, + + addAlias: function addAlias(aTraitIndex, aTraitAliasIndex) + { + let aliasesString = ""; + try { + aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex); + } + catch (e) {} + let aliases; + if (aliasesString.length) + aliases = aliasesString.split(","); + else + aliases = []; + if (aliases.indexOf(aTraitAliasIndex.toString()) == -1) + { + aliases.push(aTraitAliasIndex); + traitsBranch.setCharPref("aliases." + aTraitIndex, aliases.join()); + } + }, + + removeAlias: function removeAlias(aTraitIndex, aTraitAliasIndex) + { + let aliasesString = ""; + try { + aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex); + } + catch (e) { + return; + } + let aliases; + if (aliasesString.length) + aliases = aliasesString.split(","); + else + aliases = []; + let location; + if ((location = aliases.indexOf(aTraitAliasIndex.toString())) != -1) + { + aliases.splice(location, 1); + traitsBranch.setCharPref("aliases." + aTraitIndex, aliases.join()); + } + }, + + getAliases: function getAliases(aTraitIndex, aLength) + { + let aliasesString = ""; + try { + aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex); + } + catch (e) {} + + let aliases; + if (aliasesString.length) + aliases = aliasesString.split(","); + else + aliases = []; + aLength.value = aliases.length; + return aliases; + }, +}; + +// initialization + +_init(); + +function _init() +{ + // get existing traits + var idBranch = Services.prefs.getBranch("mailnews.traits.id."); + var nameBranch = Services.prefs.getBranch("mailnews.traits.name."); + var enabledBranch = Services.prefs.getBranch("mailnews.traits.enabled."); + var antiIdBranch = Services.prefs.getBranch("mailnews.traits.antiId."); + _lastIndex = Services.prefs.getBranch("mailnews.traits.").getIntPref("lastIndex"); + var ids = idBranch.getChildList(""); + for (var i = 0; i < ids.length; i++) + { + var id = idBranch.getCharPref(ids[i]); + var index = parseInt(ids[i]); + _registerTrait(id, index, false); + + // Read in values, ignore errors since that usually means the + // value does not exist + try { + _traits[id].name = nameBranch.getCharPref(ids[i]); + } + catch (e) {} + + try { + _traits[id].enabled = enabledBranch.getBoolPref(ids[i]); + } + catch (e) {} + + try { + _traits[id].antiId = antiIdBranch.getCharPref(ids[i]); + } + catch (e) {} + + if (_lastIndex < index) + _lastIndex = index; + } + + //for (traitId in _traits) + // dump("\nindex of " + traitId + " is " + _traits[traitId].index); + //dump("\n"); +} + +var components = [nsMsgTraitService]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/base/search/src/nsMsgTraitService.manifest b/mailnews/base/search/src/nsMsgTraitService.manifest new file mode 100644 index 000000000..0a13ee781 --- /dev/null +++ b/mailnews/base/search/src/nsMsgTraitService.manifest @@ -0,0 +1,2 @@ +component {A2E95F4F-DA72-4a41-9493-661AD353C00A} nsMsgTraitService.js +contract @mozilla.org/msg-trait-service;1 {A2E95F4F-DA72-4a41-9493-661AD353C00A} |