1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 // Implementation of db search for POP and offline IMAP mail folders
7 
8 #include "msgCore.h"
9 #include "nsIMsgDatabase.h"
10 #include "nsMsgSearchCore.h"
11 #include "nsMsgLocalSearch.h"
12 #include "nsIStreamListener.h"
13 #include "nsMsgSearchBoolExpression.h"
14 #include "nsMsgSearchTerm.h"
15 #include "nsMsgResultElement.h"
16 #include "nsIDBFolderInfo.h"
17 #include "nsMsgBaseCID.h"
18 #include "nsMsgSearchValue.h"
19 #include "nsIMsgLocalMailFolder.h"
20 #include "nsIMsgWindow.h"
21 #include "nsIMsgHdr.h"
22 #include "nsIMsgFilterPlugin.h"
23 #include "nsMsgMessageFlags.h"
24 #include "nsMsgUtils.h"
25 #include "nsIMsgFolder.h"
26 
27 extern "C" {
28 extern int MK_MSG_SEARCH_STATUS;
29 extern int MK_MSG_CANT_SEARCH_IF_NO_SUMMARY;
30 extern int MK_MSG_SEARCH_HITS_NOT_IN_DB;
31 }
32 
33 //----------------------------------------------------------------------------
34 // Class definitions for the boolean expression structure....
35 //----------------------------------------------------------------------------
36 
AddSearchTerm(nsMsgSearchBoolExpression * aOrigExpr,nsIMsgSearchTerm * aNewTerm,char * aEncodingStr)37 nsMsgSearchBoolExpression* nsMsgSearchBoolExpression::AddSearchTerm(
38     nsMsgSearchBoolExpression* aOrigExpr, nsIMsgSearchTerm* aNewTerm,
39     char* aEncodingStr)
40 // appropriately add the search term to the current expression and return a
41 // pointer to the new expression. The encodingStr is the IMAP/NNTP encoding
42 // string for newTerm.
43 {
44   return aOrigExpr->leftToRightAddTerm(aNewTerm, aEncodingStr);
45 }
46 
AddExpressionTree(nsMsgSearchBoolExpression * aOrigExpr,nsMsgSearchBoolExpression * aExpression,bool aBoolOp)47 nsMsgSearchBoolExpression* nsMsgSearchBoolExpression::AddExpressionTree(
48     nsMsgSearchBoolExpression* aOrigExpr,
49     nsMsgSearchBoolExpression* aExpression, bool aBoolOp) {
50   if (!aOrigExpr->m_term && !aOrigExpr->m_leftChild &&
51       !aOrigExpr->m_rightChild) {
52     // just use the original expression tree...
53     // delete the original since we have a new original to use
54     delete aOrigExpr;
55     return aExpression;
56   }
57 
58   nsMsgSearchBoolExpression* newExpr =
59       new nsMsgSearchBoolExpression(aOrigExpr, aExpression, aBoolOp);
60   return (newExpr) ? newExpr : aOrigExpr;
61 }
62 
nsMsgSearchBoolExpression()63 nsMsgSearchBoolExpression::nsMsgSearchBoolExpression() {
64   m_term = nullptr;
65   m_boolOp = nsMsgSearchBooleanOp::BooleanAND;
66   m_leftChild = nullptr;
67   m_rightChild = nullptr;
68 }
69 
nsMsgSearchBoolExpression(nsIMsgSearchTerm * newTerm,char * encodingStr)70 nsMsgSearchBoolExpression::nsMsgSearchBoolExpression(nsIMsgSearchTerm* newTerm,
71                                                      char* encodingStr)
72 // we are creating an expression which contains a single search term (newTerm)
73 // and the search term's IMAP or NNTP encoding value for online search
74 // expressions AND a boolean evaluation value which is used for offline search
75 // expressions.
76 {
77   m_term = newTerm;
78   m_encodingStr = encodingStr;
79   m_boolOp = nsMsgSearchBooleanOp::BooleanAND;
80 
81   // this expression does not contain sub expressions
82   m_leftChild = nullptr;
83   m_rightChild = nullptr;
84 }
85 
nsMsgSearchBoolExpression(nsMsgSearchBoolExpression * expr1,nsMsgSearchBoolExpression * expr2,nsMsgSearchBooleanOperator boolOp)86 nsMsgSearchBoolExpression::nsMsgSearchBoolExpression(
87     nsMsgSearchBoolExpression* expr1, nsMsgSearchBoolExpression* expr2,
88     nsMsgSearchBooleanOperator boolOp)
89 // we are creating an expression which contains two sub expressions and a
90 // boolean operator used to combine them.
91 {
92   m_leftChild = expr1;
93   m_rightChild = expr2;
94   m_boolOp = boolOp;
95 
96   m_term = nullptr;
97 }
98 
~nsMsgSearchBoolExpression()99 nsMsgSearchBoolExpression::~nsMsgSearchBoolExpression() {
100   // we must recursively destroy all sub expressions before we destroy
101   // ourself.....We leave search terms alone!
102   delete m_leftChild;
103   delete m_rightChild;
104 }
105 
leftToRightAddTerm(nsIMsgSearchTerm * newTerm,char * encodingStr)106 nsMsgSearchBoolExpression* nsMsgSearchBoolExpression::leftToRightAddTerm(
107     nsIMsgSearchTerm* newTerm, char* encodingStr) {
108   // we have a base case where this is the first term being added to the
109   // expression:
110   if (!m_term && !m_leftChild && !m_rightChild) {
111     m_term = newTerm;
112     m_encodingStr = encodingStr;
113     return this;
114   }
115 
116   nsMsgSearchBoolExpression* tempExpr =
117       new nsMsgSearchBoolExpression(newTerm, encodingStr);
118   if (tempExpr)  // make sure creation succeeded
119   {
120     bool booleanAnd;
121     newTerm->GetBooleanAnd(&booleanAnd);
122     nsMsgSearchBoolExpression* newExpr =
123         new nsMsgSearchBoolExpression(this, tempExpr, booleanAnd);
124     if (newExpr)
125       return newExpr;
126     else
127       delete tempExpr;  // clean up memory allocation in case of failure
128   }
129   return this;  // in case we failed to create a new expression, return self
130 }
131 
132 // returns true or false depending on what the current expression evaluates to.
OfflineEvaluate(nsIMsgDBHdr * msgToMatch,const char * defaultCharset,nsIMsgSearchScopeTerm * scope,nsIMsgDatabase * db,const nsACString & headers,bool Filtering)133 bool nsMsgSearchBoolExpression::OfflineEvaluate(nsIMsgDBHdr* msgToMatch,
134                                                 const char* defaultCharset,
135                                                 nsIMsgSearchScopeTerm* scope,
136                                                 nsIMsgDatabase* db,
137                                                 const nsACString& headers,
138                                                 bool Filtering) {
139   bool result = true;  // always default to false positives
140   bool isAnd;
141 
142   if (m_term)  // do we contain just a search term?
143   {
144     nsMsgSearchOfflineMail::ProcessSearchTerm(msgToMatch, m_term,
145                                               defaultCharset, scope, db,
146                                               headers, Filtering, &result);
147     return result;
148   }
149 
150   // otherwise we must recursively determine the value of our sub expressions
151 
152   isAnd = (m_boolOp == nsMsgSearchBooleanOp::BooleanAND);
153 
154   if (m_leftChild) {
155     result = m_leftChild->OfflineEvaluate(msgToMatch, defaultCharset, scope, db,
156                                           headers, Filtering);
157     if ((result && !isAnd) || (!result && isAnd)) return result;
158   }
159 
160   // If we got this far, either there was no leftChild (which is impossible)
161   // or we got (FALSE and OR) or (TRUE and AND) from the first result. That
162   // means the outcome depends entirely on the rightChild.
163   if (m_rightChild)
164     result = m_rightChild->OfflineEvaluate(msgToMatch, defaultCharset, scope,
165                                            db, headers, Filtering);
166 
167   return result;
168 }
169 
170 // ### Maybe we can get rid of these because of our use of nsString???
171 // constants used for online searching with IMAP/NNTP encoded search terms.
172 // the + 1 is to account for null terminators we add at each stage of assembling
173 // the expression...
174 const int sizeOfORTerm =
175     6 + 1;  // 6 bytes if we are combining two sub expressions with an OR term
176 const int sizeOfANDTerm =
177     1 + 1;  // 1 byte if we are combining two sub expressions with an AND term
178 
CalcEncodeStrSize()179 int32_t nsMsgSearchBoolExpression::CalcEncodeStrSize()
180 // recursively examine each sub expression and calculate a final size for the
181 // entire IMAP/NNTP encoding
182 {
183   if (!m_term && (!m_leftChild || !m_rightChild))  // is the expression empty?
184     return 0;
185   if (m_term)  // are we a leaf node?
186     return m_encodingStr.Length();
187   if (m_boolOp == nsMsgSearchBooleanOp::BooleanOR)
188     return sizeOfORTerm + m_leftChild->CalcEncodeStrSize() +
189            m_rightChild->CalcEncodeStrSize();
190   if (m_boolOp == nsMsgSearchBooleanOp::BooleanAND)
191     return sizeOfANDTerm + m_leftChild->CalcEncodeStrSize() +
192            m_rightChild->CalcEncodeStrSize();
193   return 0;
194 }
195 
GenerateEncodeStr(nsCString * buffer)196 void nsMsgSearchBoolExpression::GenerateEncodeStr(nsCString* buffer)
197 // recursively combine sub expressions to form a single IMAP/NNTP encoded string
198 {
199   if ((!m_term && (!m_leftChild || !m_rightChild)))  // is expression empty?
200     return;
201 
202   if (m_term)  // are we a leaf expression?
203   {
204     *buffer += m_encodingStr;
205     return;
206   }
207 
208   // add encode strings of each sub expression
209   if (m_boolOp == nsMsgSearchBooleanOp::BooleanOR) {
210     *buffer += " (OR";
211 
212     m_leftChild->GenerateEncodeStr(
213         buffer);  // insert left expression into the buffer
214     m_rightChild->GenerateEncodeStr(
215         buffer);  // insert right expression into the buffer
216 
217     // HACK ALERT!!! if last returned character in the buffer is now a ' ' then
218     // we need to remove it because we don't want a ' ' to preceded the closing
219     // paren in the OR encoding.
220     uint32_t lastCharPos = buffer->Length() - 1;
221     if (buffer->CharAt(lastCharPos) == ' ') {
222       buffer->SetLength(lastCharPos);
223     }
224 
225     *buffer += ')';
226   } else if (m_boolOp == nsMsgSearchBooleanOp::BooleanAND) {
227     m_leftChild->GenerateEncodeStr(buffer);  // insert left expression
228     m_rightChild->GenerateEncodeStr(buffer);
229   }
230   return;
231 }
232 
233 //-----------------------------------------------------------------------------
234 //---------------- Adapter class for searching offline folders ----------------
235 //-----------------------------------------------------------------------------
236 
NS_IMPL_ISUPPORTS_INHERITED(nsMsgSearchOfflineMail,nsMsgSearchAdapter,nsIUrlListener)237 NS_IMPL_ISUPPORTS_INHERITED(nsMsgSearchOfflineMail, nsMsgSearchAdapter,
238                             nsIUrlListener)
239 
240 nsMsgSearchOfflineMail::nsMsgSearchOfflineMail(
241     nsIMsgSearchScopeTerm* scope,
242     nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList)
243     : nsMsgSearchAdapter(scope, termList) {}
244 
~nsMsgSearchOfflineMail()245 nsMsgSearchOfflineMail::~nsMsgSearchOfflineMail() {
246   // Database should have been closed when the scope term finished.
247   CleanUpScope();
248   NS_ASSERTION(!m_db, "db not closed");
249 }
250 
ValidateTerms()251 nsresult nsMsgSearchOfflineMail::ValidateTerms() {
252   return nsMsgSearchAdapter::ValidateTerms();
253 }
254 
OpenSummaryFile()255 nsresult nsMsgSearchOfflineMail::OpenSummaryFile() {
256   nsCOMPtr<nsIMsgDatabase> mailDB;
257 
258   nsresult err = NS_OK;
259   // do password protection of local cache thing.
260 #ifdef DOING_FOLDER_CACHE_PASSWORDS
261   if (m_scope->m_folder &&
262       m_scope->m_folder->UserNeedsToAuthenticateForFolder(false) &&
263       m_scope->m_folder->GetMaster()->PromptForHostPassword(
264           m_scope->m_frame->GetContext(), m_scope->m_folder) != 0) {
265     m_scope->m_frame->StopRunning();
266     return SearchError_ScopeDone;
267   }
268 #endif
269   nsCOMPtr<nsIDBFolderInfo> folderInfo;
270   nsCOMPtr<nsIMsgFolder> scopeFolder;
271   err = m_scope->GetFolder(getter_AddRefs(scopeFolder));
272   if (NS_SUCCEEDED(err) && scopeFolder) {
273     err = scopeFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
274                                             getter_AddRefs(m_db));
275   } else
276     return err;  // not sure why m_folder wouldn't be set.
277 
278   if (NS_SUCCEEDED(err)) return NS_OK;
279 
280   if ((err == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) ||
281       (err == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)) {
282     nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
283         do_QueryInterface(scopeFolder, &err);
284     if (NS_SUCCEEDED(err) && localFolder) {
285       nsCOMPtr<nsIMsgSearchSession> searchSession;
286       m_scope->GetSearchSession(getter_AddRefs(searchSession));
287       if (searchSession) {
288         nsCOMPtr<nsIMsgWindow> searchWindow;
289 
290         searchSession->GetWindow(getter_AddRefs(searchWindow));
291         searchSession->PauseSearch();
292         localFolder->ParseFolder(searchWindow, this);
293       }
294     }
295   } else {
296     NS_ASSERTION(false, "unexpected error opening db");
297   }
298 
299   return err;
300 }
301 
MatchTermsForFilter(nsIMsgDBHdr * msgToMatch,nsTArray<RefPtr<nsIMsgSearchTerm>> const & termList,const char * defaultCharset,nsIMsgSearchScopeTerm * scope,nsIMsgDatabase * db,const nsACString & headers,nsMsgSearchBoolExpression ** aExpressionTree,bool * pResult)302 nsresult nsMsgSearchOfflineMail::MatchTermsForFilter(
303     nsIMsgDBHdr* msgToMatch, nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList,
304     const char* defaultCharset, nsIMsgSearchScopeTerm* scope,
305     nsIMsgDatabase* db, const nsACString& headers,
306     nsMsgSearchBoolExpression** aExpressionTree, bool* pResult) {
307   return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, headers,
308                     true, aExpressionTree, pResult);
309 }
310 
311 // static method which matches a header against a list of search terms.
MatchTermsForSearch(nsIMsgDBHdr * msgToMatch,nsTArray<RefPtr<nsIMsgSearchTerm>> const & termList,const char * defaultCharset,nsIMsgSearchScopeTerm * scope,nsIMsgDatabase * db,nsMsgSearchBoolExpression ** aExpressionTree,bool * pResult)312 nsresult nsMsgSearchOfflineMail::MatchTermsForSearch(
313     nsIMsgDBHdr* msgToMatch, nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList,
314     const char* defaultCharset, nsIMsgSearchScopeTerm* scope,
315     nsIMsgDatabase* db, nsMsgSearchBoolExpression** aExpressionTree,
316     bool* pResult) {
317   return MatchTerms(msgToMatch, termList, defaultCharset, scope, db,
318                     EmptyCString(), false, aExpressionTree, pResult);
319 }
320 
ConstructExpressionTree(nsTArray<RefPtr<nsIMsgSearchTerm>> const & termList,uint32_t termCount,uint32_t & aStartPosInList,nsMsgSearchBoolExpression ** aExpressionTree)321 nsresult nsMsgSearchOfflineMail::ConstructExpressionTree(
322     nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, uint32_t termCount,
323     uint32_t& aStartPosInList, nsMsgSearchBoolExpression** aExpressionTree) {
324   nsMsgSearchBoolExpression* finalExpression = *aExpressionTree;
325 
326   if (!finalExpression) finalExpression = new nsMsgSearchBoolExpression();
327 
328   while (aStartPosInList < termCount) {
329     nsIMsgSearchTerm* pTerm = termList[aStartPosInList];
330     NS_ASSERTION(pTerm, "couldn't get term to match");
331 
332     bool beginsGrouping;
333     bool endsGrouping;
334     pTerm->GetBeginsGrouping(&beginsGrouping);
335     pTerm->GetEndsGrouping(&endsGrouping);
336 
337     if (beginsGrouping) {
338       // temporarily turn off the grouping for our recursive call
339       pTerm->SetBeginsGrouping(false);
340       nsMsgSearchBoolExpression* innerExpression =
341           new nsMsgSearchBoolExpression();
342 
343       // the first search term in the grouping is the one that holds the
344       // operator for how this search term should be joined with the expressions
345       // to it's left.
346       bool booleanAnd;
347       pTerm->GetBooleanAnd(&booleanAnd);
348 
349       // now add this expression tree to our overall expression tree...
350       finalExpression = nsMsgSearchBoolExpression::AddExpressionTree(
351           finalExpression, innerExpression, booleanAnd);
352 
353       // recursively process this inner expression
354       ConstructExpressionTree(termList, termCount, aStartPosInList,
355                               &finalExpression->m_rightChild);
356 
357       // undo our damage
358       pTerm->SetBeginsGrouping(true);
359 
360     } else {
361       finalExpression = nsMsgSearchBoolExpression::AddSearchTerm(
362           finalExpression, pTerm,
363           nullptr);  // add the term to the expression tree
364 
365       if (endsGrouping) break;
366     }
367 
368     aStartPosInList++;
369   }  // while we still have terms to process in this group
370 
371   *aExpressionTree = finalExpression;
372 
373   return NS_OK;
374 }
375 
ProcessSearchTerm(nsIMsgDBHdr * msgToMatch,nsIMsgSearchTerm * aTerm,const char * defaultCharset,nsIMsgSearchScopeTerm * scope,nsIMsgDatabase * db,const nsACString & headers,bool Filtering,bool * pResult)376 nsresult nsMsgSearchOfflineMail::ProcessSearchTerm(
377     nsIMsgDBHdr* msgToMatch, nsIMsgSearchTerm* aTerm,
378     const char* defaultCharset, nsIMsgSearchScopeTerm* scope,
379     nsIMsgDatabase* db, const nsACString& headers, bool Filtering,
380     bool* pResult) {
381   nsresult err = NS_OK;
382   nsCString recipients;
383   nsCString ccList;
384   nsCString matchString;
385   nsCString msgCharset;
386   const char* charset;
387   bool charsetOverride = false; /* XXX BUG 68706 */
388   uint32_t msgFlags;
389   bool result;
390   bool matchAll;
391 
392   NS_ENSURE_ARG_POINTER(pResult);
393 
394   aTerm->GetMatchAll(&matchAll);
395   if (matchAll) {
396     *pResult = true;
397     return NS_OK;
398   }
399   *pResult = false;
400 
401   nsMsgSearchAttribValue attrib;
402   aTerm->GetAttrib(&attrib);
403   msgToMatch->GetCharset(getter_Copies(msgCharset));
404   charset = msgCharset.get();
405   if (!charset || !*charset) charset = (const char*)defaultCharset;
406   msgToMatch->GetFlags(&msgFlags);
407 
408   switch (attrib) {
409     case nsMsgSearchAttrib::Sender:
410       msgToMatch->GetAuthor(getter_Copies(matchString));
411       err = aTerm->MatchRfc822String(matchString, charset, &result);
412       break;
413     case nsMsgSearchAttrib::Subject: {
414       msgToMatch->GetSubject(getter_Copies(matchString) /* , true */);
415       if (msgFlags & nsMsgMessageFlags::HasRe) {
416         // Make sure we pass along the "Re: " part of the subject if this is a
417         // reply.
418         nsCString reString;
419         reString.AssignLiteral("Re: ");
420         reString.Append(matchString);
421         err = aTerm->MatchRfc2047String(reString, charset, charsetOverride,
422                                         &result);
423       } else
424         err = aTerm->MatchRfc2047String(matchString, charset, charsetOverride,
425                                         &result);
426       break;
427     }
428     case nsMsgSearchAttrib::ToOrCC: {
429       bool boolKeepGoing;
430       aTerm->GetMatchAllBeforeDeciding(&boolKeepGoing);
431       msgToMatch->GetRecipients(getter_Copies(recipients));
432       err = aTerm->MatchRfc822String(recipients, charset, &result);
433       if (boolKeepGoing == result) {
434         msgToMatch->GetCcList(getter_Copies(ccList));
435         err = aTerm->MatchRfc822String(ccList, charset, &result);
436       }
437       break;
438     }
439     case nsMsgSearchAttrib::AllAddresses: {
440       bool boolKeepGoing;
441       aTerm->GetMatchAllBeforeDeciding(&boolKeepGoing);
442       msgToMatch->GetRecipients(getter_Copies(recipients));
443       err = aTerm->MatchRfc822String(recipients, charset, &result);
444       if (boolKeepGoing == result) {
445         msgToMatch->GetCcList(getter_Copies(ccList));
446         err = aTerm->MatchRfc822String(ccList, charset, &result);
447       }
448       if (boolKeepGoing == result) {
449         msgToMatch->GetAuthor(getter_Copies(matchString));
450         err = aTerm->MatchRfc822String(matchString, charset, &result);
451       }
452       if (boolKeepGoing == result) {
453         nsCString bccList;
454         msgToMatch->GetBccList(getter_Copies(bccList));
455         err = aTerm->MatchRfc822String(bccList, charset, &result);
456       }
457       break;
458     }
459     case nsMsgSearchAttrib::Body: {
460       uint64_t messageOffset;
461       uint32_t lineCount;
462       msgToMatch->GetMessageOffset(&messageOffset);
463       msgToMatch->GetLineCount(&lineCount);
464       err = aTerm->MatchBody(scope, messageOffset, lineCount, charset,
465                              msgToMatch, db, &result);
466       break;
467     }
468     case nsMsgSearchAttrib::Date: {
469       PRTime date;
470       msgToMatch->GetDate(&date);
471       err = aTerm->MatchDate(date, &result);
472 
473       break;
474     }
475     case nsMsgSearchAttrib::HasAttachmentStatus:
476     case nsMsgSearchAttrib::MsgStatus:
477       err = aTerm->MatchStatus(msgFlags, &result);
478       break;
479     case nsMsgSearchAttrib::Priority: {
480       nsMsgPriorityValue msgPriority;
481       msgToMatch->GetPriority(&msgPriority);
482       err = aTerm->MatchPriority(msgPriority, &result);
483       break;
484     }
485     case nsMsgSearchAttrib::Size: {
486       uint32_t messageSize;
487       msgToMatch->GetMessageSize(&messageSize);
488       err = aTerm->MatchSize(messageSize, &result);
489       break;
490     }
491     case nsMsgSearchAttrib::To:
492       msgToMatch->GetRecipients(getter_Copies(recipients));
493       err = aTerm->MatchRfc822String(recipients, charset, &result);
494       break;
495     case nsMsgSearchAttrib::CC:
496       msgToMatch->GetCcList(getter_Copies(ccList));
497       err = aTerm->MatchRfc822String(ccList, charset, &result);
498       break;
499     case nsMsgSearchAttrib::AgeInDays: {
500       PRTime date;
501       msgToMatch->GetDate(&date);
502       err = aTerm->MatchAge(date, &result);
503       break;
504     }
505     case nsMsgSearchAttrib::Label: {
506       nsMsgLabelValue label;
507       msgToMatch->GetLabel(&label);
508       err = aTerm->MatchLabel(label, &result);
509       break;
510     }
511     case nsMsgSearchAttrib::Keywords: {
512       nsCString keywords;
513       nsMsgLabelValue label;
514       msgToMatch->GetStringProperty("keywords", getter_Copies(keywords));
515       msgToMatch->GetLabel(&label);
516       if (label >= 1) {
517         if (!keywords.IsEmpty()) keywords.Append(' ');
518         keywords.AppendLiteral("$label");
519         keywords.Append(label + '0');
520       }
521       err = aTerm->MatchKeyword(keywords, &result);
522       break;
523     }
524     case nsMsgSearchAttrib::JunkStatus: {
525       nsCString junkScoreStr;
526       msgToMatch->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
527       err = aTerm->MatchJunkStatus(junkScoreStr.get(), &result);
528       break;
529     }
530     case nsMsgSearchAttrib::JunkPercent: {
531       // When the junk status is set by the plugin, use junkpercent (if
532       // available) Otherwise, use the limits (0 or 100) depending on the
533       // junkscore.
534       uint32_t junkPercent;
535       nsresult rv;
536       nsCString junkScoreOriginStr;
537       nsCString junkPercentStr;
538       msgToMatch->GetStringProperty("junkscoreorigin",
539                                     getter_Copies(junkScoreOriginStr));
540       msgToMatch->GetStringProperty("junkpercent",
541                                     getter_Copies(junkPercentStr));
542       if (junkScoreOriginStr.EqualsLiteral("plugin") &&
543           !junkPercentStr.IsEmpty()) {
544         junkPercent = junkPercentStr.ToInteger(&rv);
545         NS_ENSURE_SUCCESS(rv, rv);
546       } else {
547         nsCString junkScoreStr;
548         msgToMatch->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
549         // When junk status is not set (uncertain) we'll set the value to ham.
550         if (junkScoreStr.IsEmpty())
551           junkPercent = nsIJunkMailPlugin::IS_HAM_SCORE;
552         else {
553           junkPercent = junkScoreStr.ToInteger(&rv);
554           NS_ENSURE_SUCCESS(rv, rv);
555         }
556       }
557       err = aTerm->MatchJunkPercent(junkPercent, &result);
558       break;
559     }
560     case nsMsgSearchAttrib::JunkScoreOrigin: {
561       nsCString junkScoreOriginStr;
562       msgToMatch->GetStringProperty("junkscoreorigin",
563                                     getter_Copies(junkScoreOriginStr));
564       err = aTerm->MatchJunkScoreOrigin(junkScoreOriginStr.get(), &result);
565       break;
566     }
567     case nsMsgSearchAttrib::HdrProperty: {
568       err = aTerm->MatchHdrProperty(msgToMatch, &result);
569       break;
570     }
571     case nsMsgSearchAttrib::Uint32HdrProperty: {
572       err = aTerm->MatchUint32HdrProperty(msgToMatch, &result);
573       break;
574     }
575     case nsMsgSearchAttrib::Custom: {
576       err = aTerm->MatchCustom(msgToMatch, &result);
577       break;
578     }
579     case nsMsgSearchAttrib::FolderFlag: {
580       err = aTerm->MatchFolderFlag(msgToMatch, &result);
581       break;
582     }
583     default:
584       // XXX todo
585       // for the temporary return receipts filters, we use a custom header for
586       // Content-Type but unlike the other custom headers, this one doesn't show
587       // up in the search / filter UI.  we set the attrib to be
588       // nsMsgSearchAttrib::OtherHeader, where as for user defined custom
589       // headers start at nsMsgSearchAttrib::OtherHeader + 1 Not sure if there
590       // is a better way to do this yet.  Maybe reserve the last custom header
591       // for ::Content-Type?  But if we do, make sure that change doesn't cause
592       // nsMsgFilter::GetTerm() to change, and start making us ask IMAP servers
593       // for the Content-Type header on all messages.
594       if (attrib >= nsMsgSearchAttrib::OtherHeader &&
595           attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) {
596         uint32_t lineCount;
597         msgToMatch->GetLineCount(&lineCount);
598         uint64_t messageOffset;
599         msgToMatch->GetMessageOffset(&messageOffset);
600         err = aTerm->MatchArbitraryHeader(scope, lineCount, charset,
601                                           charsetOverride, msgToMatch, db,
602                                           headers, Filtering, &result);
603       } else {
604         err = NS_ERROR_INVALID_ARG;  // ### was SearchError_InvalidAttribute
605         result = false;
606       }
607   }
608 
609   *pResult = result;
610   return err;
611 }
612 
MatchTerms(nsIMsgDBHdr * msgToMatch,nsTArray<RefPtr<nsIMsgSearchTerm>> const & termList,const char * defaultCharset,nsIMsgSearchScopeTerm * scope,nsIMsgDatabase * db,const nsACString & headers,bool Filtering,nsMsgSearchBoolExpression ** aExpressionTree,bool * pResult)613 nsresult nsMsgSearchOfflineMail::MatchTerms(
614     nsIMsgDBHdr* msgToMatch, nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList,
615     const char* defaultCharset, nsIMsgSearchScopeTerm* scope,
616     nsIMsgDatabase* db, const nsACString& headers, bool Filtering,
617     nsMsgSearchBoolExpression** aExpressionTree, bool* pResult) {
618   NS_ENSURE_ARG(aExpressionTree);
619   nsresult err;
620 
621   if (!*aExpressionTree) {
622     uint32_t initialPos = 0;
623     uint32_t count = termList.Length();
624     err = ConstructExpressionTree(termList, count, initialPos, aExpressionTree);
625     if (NS_FAILED(err)) return err;
626   }
627 
628   // evaluate the expression tree and return the result
629   *pResult = (*aExpressionTree)
630                  ? (*aExpressionTree)
631                        ->OfflineEvaluate(msgToMatch, defaultCharset, scope, db,
632                                          headers, Filtering)
633                  : true;  // vacuously true...
634 
635   return NS_OK;
636 }
637 
Search(bool * aDone)638 nsresult nsMsgSearchOfflineMail::Search(bool* aDone) {
639   nsresult err = NS_OK;
640 
641   NS_ENSURE_ARG(aDone);
642   nsresult dbErr = NS_OK;
643   nsMsgSearchBoolExpression* expressionTree = nullptr;
644 
645   const uint32_t kTimeSliceInMS = 200;
646 
647   *aDone = false;
648   // Try to open the DB lazily. This will set up a parser if one is required
649   if (!m_db) err = OpenSummaryFile();
650   if (!m_db)  // must be reparsing.
651     return err;
652 
653   // Reparsing is unnecessary or completed
654   if (NS_SUCCEEDED(err)) {
655     if (!m_listContext)
656       dbErr = m_db->ReverseEnumerateMessages(getter_AddRefs(m_listContext));
657     if (NS_SUCCEEDED(dbErr) && m_listContext) {
658       PRIntervalTime startTime = PR_IntervalNow();
659       while (!*aDone)  // we'll break out of the loop after kTimeSliceInMS
660                        // milliseconds
661       {
662         nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
663         dbErr = m_listContext->GetNext(getter_AddRefs(msgDBHdr));
664         if (NS_FAILED(dbErr))
665           *aDone = true;  //###phil dbErr is dropped on the floor. just note
666                           // that we did have an error so we'll clean up later
667         else {
668           bool match = false;
669           nsAutoString nullCharset, folderCharset;
670           GetSearchCharsets(nullCharset, folderCharset);
671           NS_ConvertUTF16toUTF8 charset(folderCharset);
672           // Is this message a hit?
673           err = MatchTermsForSearch(msgDBHdr, m_searchTerms, charset.get(),
674                                     m_scope, m_db, &expressionTree, &match);
675           // Add search hits to the results list
676           if (NS_SUCCEEDED(err) && match) {
677             AddResultElement(msgDBHdr);
678           }
679           PRIntervalTime elapsedTime = PR_IntervalNow() - startTime;
680           // check if more than kTimeSliceInMS milliseconds have elapsed in this
681           // time slice started
682           if (PR_IntervalToMilliseconds(elapsedTime) > kTimeSliceInMS) break;
683         }
684       }
685     }
686   } else
687     *aDone = true;  // we couldn't open up the DB. This is an unrecoverable
688                     // error so mark the scope as done.
689 
690   delete expressionTree;
691 
692   // in the past an error here would cause an "infinite" search because the url
693   // would continue to run... i.e. if we couldn't open the database, it returns
694   // an error code but the caller of this function says, oh, we did not finish
695   // so continue...what we really want is to treat this current scope as done
696   if (*aDone) CleanUpScope();  // Do clean up for end-of-scope processing
697   return err;
698 }
699 
CleanUpScope()700 void nsMsgSearchOfflineMail::CleanUpScope() {
701   // Let go of the DB when we're done with it so we don't kill the db cache
702   if (m_db) {
703     m_listContext = nullptr;
704     m_db->Close(false);
705   }
706   m_db = nullptr;
707 
708   if (m_scope) m_scope->CloseInputStream();
709 }
710 
AddResultElement(nsIMsgDBHdr * pHeaders)711 NS_IMETHODIMP nsMsgSearchOfflineMail::AddResultElement(nsIMsgDBHdr* pHeaders) {
712   nsresult err = NS_OK;
713 
714   nsCOMPtr<nsIMsgSearchSession> searchSession;
715   m_scope->GetSearchSession(getter_AddRefs(searchSession));
716   if (searchSession) {
717     nsCOMPtr<nsIMsgFolder> scopeFolder;
718     err = m_scope->GetFolder(getter_AddRefs(scopeFolder));
719     searchSession->AddSearchHit(pHeaders, scopeFolder);
720   }
721   return err;
722 }
723 
724 NS_IMETHODIMP
Abort()725 nsMsgSearchOfflineMail::Abort() {
726   // Let go of the DB when we're done with it so we don't kill the db cache
727   if (m_db) m_db->Close(true /* commit in case we downloaded new headers */);
728   m_db = nullptr;
729   return nsMsgSearchAdapter::Abort();
730 }
731 
732 /* void OnStartRunningUrl (in nsIURI url); */
OnStartRunningUrl(nsIURI * url)733 NS_IMETHODIMP nsMsgSearchOfflineMail::OnStartRunningUrl(nsIURI* url) {
734   return NS_OK;
735 }
736 
737 /* void OnStopRunningUrl (in nsIURI url, in nsresult aExitCode); */
OnStopRunningUrl(nsIURI * url,nsresult aExitCode)738 NS_IMETHODIMP nsMsgSearchOfflineMail::OnStopRunningUrl(nsIURI* url,
739                                                        nsresult aExitCode) {
740   nsCOMPtr<nsIMsgSearchSession> searchSession;
741   if (m_scope) m_scope->GetSearchSession(getter_AddRefs(searchSession));
742   if (searchSession) searchSession->ResumeSearch();
743 
744   return NS_OK;
745 }
746 
nsMsgSearchOfflineNews(nsIMsgSearchScopeTerm * scope,nsTArray<RefPtr<nsIMsgSearchTerm>> const & termList)747 nsMsgSearchOfflineNews::nsMsgSearchOfflineNews(
748     nsIMsgSearchScopeTerm* scope,
749     nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList)
750     : nsMsgSearchOfflineMail(scope, termList) {}
751 
~nsMsgSearchOfflineNews()752 nsMsgSearchOfflineNews::~nsMsgSearchOfflineNews() {}
753 
OpenSummaryFile()754 nsresult nsMsgSearchOfflineNews::OpenSummaryFile() {
755   nsresult err = NS_OK;
756   nsCOMPtr<nsIDBFolderInfo> folderInfo;
757   nsCOMPtr<nsIMsgFolder> scopeFolder;
758   err = m_scope->GetFolder(getter_AddRefs(scopeFolder));
759   // code here used to check if offline store existed, which breaks offline
760   // news.
761   if (NS_SUCCEEDED(err) && scopeFolder)
762     err = scopeFolder->GetMsgDatabase(getter_AddRefs(m_db));
763   return err;
764 }
765 
ValidateTerms()766 nsresult nsMsgSearchOfflineNews::ValidateTerms() {
767   return nsMsgSearchOfflineMail::ValidateTerms();
768 }
769 
770 // local helper functions to set subsets of the validity table
771 // clang-format off
SetJunk(nsIMsgSearchValidityTable * aTable)772 nsresult SetJunk(nsIMsgSearchValidityTable *aTable) {
773   NS_ENSURE_ARG_POINTER(aTable);
774 
775   aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1);
776   aTable->SetEnabled  (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1);
777   aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1);
778   aTable->SetEnabled  (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1);
779   aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1);
780   aTable->SetEnabled  (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1);
781   aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1);
782   aTable->SetEnabled  (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1);
783 
784   aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1);
785   aTable->SetEnabled  (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1);
786   aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1);
787   aTable->SetEnabled  (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1);
788   aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1);
789   aTable->SetEnabled  (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1);
790 
791   aTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1);
792   aTable->SetEnabled  (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1);
793   aTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1);
794   aTable->SetEnabled  (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1);
795 
796   return NS_OK;
797 }
798 
SetBody(nsIMsgSearchValidityTable * aTable)799 nsresult SetBody(nsIMsgSearchValidityTable* aTable) {
800   NS_ENSURE_ARG_POINTER(aTable);
801 
802   aTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
803   aTable->SetEnabled  (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
804   aTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
805   aTable->SetEnabled  (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
806   aTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1);
807   aTable->SetEnabled  (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1);
808   aTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1);
809   aTable->SetEnabled  (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1);
810 
811   return NS_OK;
812 }
813 
814 // set the base validity table values for local news
SetLocalNews(nsIMsgSearchValidityTable * aTable)815 nsresult SetLocalNews(nsIMsgSearchValidityTable* aTable) {
816   NS_ENSURE_ARG_POINTER(aTable);
817 
818   aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
819   aTable->SetEnabled  (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
820   aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
821   aTable->SetEnabled  (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
822   aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
823   aTable->SetEnabled  (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
824   aTable->SetEnabled  (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
825   aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
826   aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
827   aTable->SetEnabled  (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
828   aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
829   aTable->SetEnabled  (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
830   aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
831   aTable->SetEnabled  (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
832   aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
833   aTable->SetEnabled  (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
834 
835   aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
836   aTable->SetEnabled  (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
837   aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
838   aTable->SetEnabled  (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
839   aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
840   aTable->SetEnabled  (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
841   aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
842   aTable->SetEnabled  (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
843   aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
844   aTable->SetEnabled  (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
845   aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
846   aTable->SetEnabled  (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
847 
848   aTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
849   aTable->SetEnabled  (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
850   aTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
851   aTable->SetEnabled  (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
852   aTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
853   aTable->SetEnabled  (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
854   aTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
855   aTable->SetEnabled  (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
856 
857   aTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
858   aTable->SetEnabled  (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
859   aTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan,  1);
860   aTable->SetEnabled  (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
861   aTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is,  1);
862   aTable->SetEnabled  (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
863 
864   aTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
865   aTable->SetEnabled  (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
866   aTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
867   aTable->SetEnabled  (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
868 
869   aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
870   aTable->SetEnabled  (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
871   aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
872   aTable->SetEnabled  (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
873   aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
874   aTable->SetEnabled  (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
875   aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
876   aTable->SetEnabled  (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
877   aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
878   aTable->SetEnabled  (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
879   aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
880   aTable->SetEnabled  (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
881 
882   aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
883   aTable->SetEnabled  (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
884   aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
885   aTable->SetEnabled  (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
886   aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
887   aTable->SetEnabled  (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
888   aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
889   aTable->SetEnabled  (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
890   aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
891   aTable->SetEnabled  (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
892   aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
893   aTable->SetEnabled  (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
894   return NS_OK;
895 }
896 // clang-format on
897 
InitLocalNewsTable()898 nsresult nsMsgSearchValidityManager::InitLocalNewsTable() {
899   NS_ASSERTION(nullptr == m_localNewsTable,
900                "already have local news validity table");
901   nsresult rv = NewTable(getter_AddRefs(m_localNewsTable));
902   NS_ENSURE_SUCCESS(rv, rv);
903   return SetLocalNews(m_localNewsTable);
904 }
905 
InitLocalNewsBodyTable()906 nsresult nsMsgSearchValidityManager::InitLocalNewsBodyTable() {
907   NS_ASSERTION(nullptr == m_localNewsBodyTable,
908                "already have local news+body validity table");
909   nsresult rv = NewTable(getter_AddRefs(m_localNewsBodyTable));
910   NS_ENSURE_SUCCESS(rv, rv);
911   rv = SetLocalNews(m_localNewsBodyTable);
912   NS_ENSURE_SUCCESS(rv, rv);
913   return SetBody(m_localNewsBodyTable);
914 }
915 
InitLocalNewsJunkTable()916 nsresult nsMsgSearchValidityManager::InitLocalNewsJunkTable() {
917   NS_ASSERTION(nullptr == m_localNewsJunkTable,
918                "already have local news+junk validity table");
919   nsresult rv = NewTable(getter_AddRefs(m_localNewsJunkTable));
920   NS_ENSURE_SUCCESS(rv, rv);
921   rv = SetLocalNews(m_localNewsJunkTable);
922   NS_ENSURE_SUCCESS(rv, rv);
923   return SetJunk(m_localNewsJunkTable);
924 }
925 
InitLocalNewsJunkBodyTable()926 nsresult nsMsgSearchValidityManager::InitLocalNewsJunkBodyTable() {
927   NS_ASSERTION(nullptr == m_localNewsJunkBodyTable,
928                "already have local news+junk+body validity table");
929   nsresult rv = NewTable(getter_AddRefs(m_localNewsJunkBodyTable));
930   NS_ENSURE_SUCCESS(rv, rv);
931   rv = SetLocalNews(m_localNewsJunkBodyTable);
932   NS_ENSURE_SUCCESS(rv, rv);
933   rv = SetJunk(m_localNewsJunkBodyTable);
934   NS_ENSURE_SUCCESS(rv, rv);
935   return SetBody(m_localNewsJunkBodyTable);
936 }
937