summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/search/src/nsMsgLocalSearch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/search/src/nsMsgLocalSearch.cpp')
-rw-r--r--comm/mailnews/search/src/nsMsgLocalSearch.cpp919
1 files changed, 919 insertions, 0 deletions
diff --git a/comm/mailnews/search/src/nsMsgLocalSearch.cpp b/comm/mailnews/search/src/nsMsgLocalSearch.cpp
new file mode 100644
index 0000000000..e43c0dd9d9
--- /dev/null
+++ b/comm/mailnews/search/src/nsMsgLocalSearch.cpp
@@ -0,0 +1,919 @@
+/* -*- 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/. */
+
+// 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"
+#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 nsACString& headers,
+ 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, 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, 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, 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)
+// recursively 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,
+ nsTArray<RefPtr<nsIMsgSearchTerm>> const& 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, nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList,
+ const char* defaultCharset, nsIMsgSearchScopeTerm* scope,
+ nsIMsgDatabase* db, const nsACString& headers,
+ nsMsgSearchBoolExpression** aExpressionTree, bool* pResult) {
+ return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, headers,
+ true, aExpressionTree, pResult);
+}
+
+// static method which matches a header against a list of search terms.
+nsresult nsMsgSearchOfflineMail::MatchTermsForSearch(
+ nsIMsgDBHdr* msgToMatch, nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList,
+ const char* defaultCharset, nsIMsgSearchScopeTerm* scope,
+ nsIMsgDatabase* db, nsMsgSearchBoolExpression** aExpressionTree,
+ bool* pResult) {
+ return MatchTerms(msgToMatch, termList, defaultCharset, scope, db,
+ EmptyCString(), false, aExpressionTree, pResult);
+}
+
+nsresult nsMsgSearchOfflineMail::ConstructExpressionTree(
+ nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, uint32_t termCount,
+ uint32_t& aStartPosInList, nsMsgSearchBoolExpression** aExpressionTree) {
+ nsMsgSearchBoolExpression* finalExpression = *aExpressionTree;
+
+ if (!finalExpression) finalExpression = new nsMsgSearchBoolExpression();
+
+ while (aStartPosInList < termCount) {
+ nsIMsgSearchTerm* pTerm = termList[aStartPosInList];
+ 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 nsACString& headers, 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(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.AssignLiteral("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::Keywords: {
+ nsCString keywords;
+ msgToMatch->GetStringProperty("keywords", keywords);
+ err = aTerm->MatchKeyword(keywords, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::JunkStatus: {
+ nsCString junkScoreStr;
+ msgToMatch->GetStringProperty("junkscore", 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", junkScoreOriginStr);
+ msgToMatch->GetStringProperty("junkpercent", junkPercentStr);
+ if (junkScoreOriginStr.EqualsLiteral("plugin") &&
+ !junkPercentStr.IsEmpty()) {
+ junkPercent = junkPercentStr.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsCString junkScoreStr;
+ msgToMatch->GetStringProperty("junkscore", 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", 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, Filtering, &result);
+ } else {
+ err = NS_ERROR_INVALID_ARG; // ### was SearchError_InvalidAttribute
+ result = false;
+ }
+ }
+
+ *pResult = result;
+ return err;
+}
+
+nsresult nsMsgSearchOfflineMail::MatchTerms(
+ nsIMsgDBHdr* msgToMatch, nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList,
+ const char* defaultCharset, nsIMsgSearchScopeTerm* scope,
+ nsIMsgDatabase* db, const nsACString& headers, bool Filtering,
+ nsMsgSearchBoolExpression** aExpressionTree, bool* pResult) {
+ NS_ENSURE_ARG(aExpressionTree);
+ nsresult err;
+
+ if (!*aExpressionTree) {
+ uint32_t initialPos = 0;
+ uint32_t count = termList.Length();
+ 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, Filtering)
+ : true; // vacuously true...
+
+ return NS_OK;
+}
+
+nsresult nsMsgSearchOfflineMail::Search(bool* aDone) {
+ nsresult err = NS_OK;
+
+ NS_ENSURE_ARG(aDone);
+ nsresult dbErr = NS_OK;
+ 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<nsIMsgDBHdr> msgDBHdr;
+ dbErr = m_listContext->GetNext(getter_AddRefs(msgDBHdr));
+ 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,
+ nsTArray<RefPtr<nsIMsgSearchTerm>> const& 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
+// clang-format off
+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;
+}
+// clang-format on
+
+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);
+}