summaryrefslogtreecommitdiffstats
path: root/mailnews/base/search/src
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/search/src')
-rw-r--r--mailnews/base/search/src/Bogofilter.sfd14
-rw-r--r--mailnews/base/search/src/DSPAM.sfd14
-rw-r--r--mailnews/base/search/src/Habeas.sfd8
-rw-r--r--mailnews/base/search/src/POPFile.sfd14
-rw-r--r--mailnews/base/search/src/SpamAssassin.sfd14
-rw-r--r--mailnews/base/search/src/SpamCatcher.sfd14
-rw-r--r--mailnews/base/search/src/SpamPal.sfd14
-rw-r--r--mailnews/base/search/src/moz.build33
-rw-r--r--mailnews/base/search/src/nsMsgBodyHandler.cpp487
-rw-r--r--mailnews/base/search/src/nsMsgFilter.cpp1057
-rw-r--r--mailnews/base/search/src/nsMsgFilter.h103
-rw-r--r--mailnews/base/search/src/nsMsgFilterList.cpp1198
-rw-r--r--mailnews/base/search/src/nsMsgFilterList.h75
-rw-r--r--mailnews/base/search/src/nsMsgFilterService.cpp1216
-rw-r--r--mailnews/base/search/src/nsMsgFilterService.h46
-rw-r--r--mailnews/base/search/src/nsMsgImapSearch.cpp1004
-rw-r--r--mailnews/base/search/src/nsMsgLocalSearch.cpp1022
-rw-r--r--mailnews/base/search/src/nsMsgLocalSearch.h104
-rw-r--r--mailnews/base/search/src/nsMsgSearchAdapter.cpp1332
-rw-r--r--mailnews/base/search/src/nsMsgSearchImap.h37
-rw-r--r--mailnews/base/search/src/nsMsgSearchNews.cpp511
-rw-r--r--mailnews/base/search/src/nsMsgSearchNews.h49
-rw-r--r--mailnews/base/search/src/nsMsgSearchSession.cpp675
-rw-r--r--mailnews/base/search/src/nsMsgSearchSession.h98
-rw-r--r--mailnews/base/search/src/nsMsgSearchTerm.cpp2088
-rw-r--r--mailnews/base/search/src/nsMsgSearchValue.cpp117
-rw-r--r--mailnews/base/search/src/nsMsgSearchValue.h26
-rw-r--r--mailnews/base/search/src/nsMsgTraitService.js239
-rw-r--r--mailnews/base/search/src/nsMsgTraitService.manifest2
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..09442aeea
--- /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(static_cast<const char16_t*>(pchar.get()));
+ }
+ }
+ }
+// else
+// asciiOnly = false; // TODO: enable this line when the condition is not a plain "true" in the if().
+
+ const char16_t* usAsciiCharSet = u"us-ascii";
+ // Get the optional CHARSET parameter, in case we need it.
+ char *csname = GetImapCharsetParam(asciiOnly ? usAsciiCharSet : 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 : destCharset,
+ asciiOnly ? usAsciiCharSet : 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..3d08d27fa
--- /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:
+ explicit 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}