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 #include "msgCore.h"
7 #include "prmem.h"
8 #include "nsMsgSearchCore.h"
9 #include "nsIMsgSearchSession.h"
10 #include "nsMsgUtils.h"
11 #include "nsIMsgDatabase.h"
12 #include "nsIMsgHdr.h"
13 #include "nsMsgSearchTerm.h"
14 #include "nsMsgSearchScopeTerm.h"
15 #include "nsMsgBodyHandler.h"
16 #include "nsMsgResultElement.h"
17 #include "nsIMsgImapMailFolder.h"
18 #include "nsMsgSearchImap.h"
19 #include "nsMsgLocalSearch.h"
20 #include "nsMsgSearchNews.h"
21 #include "nsMsgSearchValue.h"
22 #include "nsMsgI18N.h"
23 #include "nsIMimeConverter.h"
24 #include "nsMsgMimeCID.h"
25 #include "nsIPrefBranch.h"
26 #include "nsIPrefService.h"
27 #include "nsIMsgFilterPlugin.h"
28 #include "nsIFile.h"
29 #include "nsISeekableStream.h"
30 #include "nsNetCID.h"
31 #include "nsIFileStreams.h"
32 #include "nsUnicharUtils.h"
33 #include "nsIAbCard.h"
34 #include "nsServiceManagerUtils.h"
35 #include "nsComponentManagerUtils.h"
36 #include <ctype.h>
37 #include "nsMsgBaseCID.h"
38 #include "nsIMsgTagService.h"
39 #include "nsMsgMessageFlags.h"
40 #include "nsIMsgFilterService.h"
41 #include "nsIMsgPluggableStore.h"
42 #include "nsAbBaseCID.h"
43 #include "nsIAbManager.h"
44 #include "mozilla/ArrayUtils.h"
45 #include "mozilla/mailnews/MimeHeaderParser.h"
46 #include "mozilla/Utf8.h"
47 
48 using namespace mozilla::mailnews;
49 
50 //---------------------------------------------------------------------------
51 // nsMsgSearchTerm specifies one criterion, e.g. name contains phil
52 //---------------------------------------------------------------------------
53 
54 //-----------------------------------------------------------------------------
55 //-------------------- Implementation of nsMsgSearchTerm -----------------------
56 //-----------------------------------------------------------------------------
57 #define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders"
58 
59 typedef struct {
60   nsMsgSearchAttribValue attrib;
61   const char* attribName;
62 } nsMsgSearchAttribEntry;
63 
64 nsMsgSearchAttribEntry SearchAttribEntryTable[] = {
65     {nsMsgSearchAttrib::Subject, "subject"},
66     {nsMsgSearchAttrib::Sender, "from"},
67     {nsMsgSearchAttrib::Body, "body"},
68     {nsMsgSearchAttrib::Date, "date"},
69     {nsMsgSearchAttrib::Priority, "priority"},
70     {nsMsgSearchAttrib::MsgStatus, "status"},
71     {nsMsgSearchAttrib::To, "to"},
72     {nsMsgSearchAttrib::CC, "cc"},
73     {nsMsgSearchAttrib::ToOrCC, "to or cc"},
74     {nsMsgSearchAttrib::AllAddresses, "all addresses"},
75     {nsMsgSearchAttrib::AgeInDays, "age in days"},
76     {nsMsgSearchAttrib::Label, "label"},
77     {nsMsgSearchAttrib::Keywords, "tag"},
78     {nsMsgSearchAttrib::Size, "size"},
79     // this used to be nsMsgSearchAttrib::SenderInAddressBook
80     // we used to have two Sender menuitems
81     // for backward compatibility, we can still parse
82     // the old style.  see bug #179803
83     {nsMsgSearchAttrib::Sender, "from in ab"},
84     {nsMsgSearchAttrib::JunkStatus, "junk status"},
85     {nsMsgSearchAttrib::JunkPercent, "junk percent"},
86     {nsMsgSearchAttrib::JunkScoreOrigin, "junk score origin"},
87     {nsMsgSearchAttrib::HasAttachmentStatus, "has attachment status"},
88 };
89 
90 static const unsigned int sNumSearchAttribEntryTable =
91     MOZ_ARRAY_LENGTH(SearchAttribEntryTable);
92 
93 // Take a string which starts off with an attribute
94 // and return the matching attribute. If the string is not in the table, and it
95 // begins with a quote, then we can conclude that it is an arbitrary header.
96 // Otherwise if not in the table, it is the id for a custom search term.
NS_MsgGetAttributeFromString(const char * string,nsMsgSearchAttribValue * attrib,nsACString & aCustomId)97 nsresult NS_MsgGetAttributeFromString(const char* string,
98                                       nsMsgSearchAttribValue* attrib,
99                                       nsACString& aCustomId) {
100   NS_ENSURE_ARG_POINTER(string);
101   NS_ENSURE_ARG_POINTER(attrib);
102 
103   bool found = false;
104   bool isArbitraryHeader = false;
105   // arbitrary headers have a leading quote
106   if (*string != '"') {
107     for (unsigned int idxAttrib = 0; idxAttrib < sNumSearchAttribEntryTable;
108          idxAttrib++) {
109       if (!PL_strcasecmp(string,
110                          SearchAttribEntryTable[idxAttrib].attribName)) {
111         found = true;
112         *attrib = SearchAttribEntryTable[idxAttrib].attrib;
113         break;
114       }
115     }
116   } else  // remove the leading quote
117   {
118     string++;
119     isArbitraryHeader = true;
120   }
121 
122   if (!found && !isArbitraryHeader) {
123     // must be a custom attribute
124     *attrib = nsMsgSearchAttrib::Custom;
125     aCustomId.Assign(string);
126     return NS_OK;
127   }
128 
129   if (!found) {
130     nsresult rv;
131     bool goodHdr;
132     IsRFC822HeaderFieldName(string, &goodHdr);
133     if (!goodHdr) return NS_MSG_INVALID_CUSTOM_HEADER;
134     // 49 is for showing customize... in ui, headers start from 50 onwards up
135     // until 99.
136     *attrib = nsMsgSearchAttrib::OtherHeader + 1;
137 
138     nsCOMPtr<nsIPrefService> prefService =
139         do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
140     NS_ENSURE_SUCCESS(rv, rv);
141 
142     nsCOMPtr<nsIPrefBranch> prefBranch;
143     rv = prefService->GetBranch(nullptr, getter_AddRefs(prefBranch));
144     NS_ENSURE_SUCCESS(rv, rv);
145 
146     nsCString headers;
147     prefBranch->GetCharPref(MAILNEWS_CUSTOM_HEADERS, headers);
148 
149     if (!headers.IsEmpty()) {
150       nsAutoCString hdrStr(headers);
151       hdrStr.StripWhitespace();  // remove whitespace before parsing
152 
153       char* newStr = hdrStr.BeginWriting();
154       char* token = NS_strtok(":", &newStr);
155       uint32_t i = 0;
156       while (token) {
157         if (PL_strcasecmp(token, string) == 0) {
158           *attrib += i;  // we found custom header in the pref
159           found = true;
160           break;
161         }
162         token = NS_strtok(":", &newStr);
163         i++;
164       }
165     }
166   }
167   // If we didn't find the header in MAILNEWS_CUSTOM_HEADERS, we're
168   // going to return NS_OK and an attrib of nsMsgSearchAttrib::OtherHeader+1.
169   // in case it's a client side spam filter description filter,
170   // which doesn't add its headers to mailnews.customMailHeaders.
171   // We've already checked that it's a valid header and returned
172   // an error if so.
173 
174   return NS_OK;
175 }
176 
GetAttributeFromString(const char * aString,nsMsgSearchAttribValue * aAttrib)177 NS_IMETHODIMP nsMsgSearchTerm::GetAttributeFromString(
178     const char* aString, nsMsgSearchAttribValue* aAttrib) {
179   nsAutoCString customId;
180   return NS_MsgGetAttributeFromString(aString, aAttrib, customId);
181 }
182 
NS_MsgGetStringForAttribute(int16_t attrib,const char ** string)183 nsresult NS_MsgGetStringForAttribute(int16_t attrib, const char** string) {
184   NS_ENSURE_ARG_POINTER(string);
185 
186   bool found = false;
187   for (unsigned int idxAttrib = 0; idxAttrib < sNumSearchAttribEntryTable;
188        idxAttrib++) {
189     // I'm using the idx's as aliases into MSG_SearchAttribute and
190     // MSG_SearchOperator enums which is legal because of the way the
191     // enums are defined (starts at 0, numItems at end)
192     if (attrib == SearchAttribEntryTable[idxAttrib].attrib) {
193       found = true;
194       *string = SearchAttribEntryTable[idxAttrib].attribName;
195       break;
196     }
197   }
198   if (!found) *string = "";  // don't leave the string uninitialized
199 
200   // we no longer return invalid attribute. If we cannot find the string in the
201   // table, then it is an arbitrary header. Return success regardless if found
202   // or not
203   return NS_OK;
204 }
205 
206 typedef struct {
207   nsMsgSearchOpValue op;
208   const char* opName;
209 } nsMsgSearchOperatorEntry;
210 
211 nsMsgSearchOperatorEntry SearchOperatorEntryTable[] = {
212     {nsMsgSearchOp::Contains, "contains"},
213     {nsMsgSearchOp::DoesntContain, "doesn't contain"},
214     {nsMsgSearchOp::Is, "is"},
215     {nsMsgSearchOp::Isnt, "isn't"},
216     {nsMsgSearchOp::IsEmpty, "is empty"},
217     {nsMsgSearchOp::IsntEmpty, "isn't empty"},
218     {nsMsgSearchOp::IsBefore, "is before"},
219     {nsMsgSearchOp::IsAfter, "is after"},
220     {nsMsgSearchOp::IsHigherThan, "is higher than"},
221     {nsMsgSearchOp::IsLowerThan, "is lower than"},
222     {nsMsgSearchOp::BeginsWith, "begins with"},
223     {nsMsgSearchOp::EndsWith, "ends with"},
224     {nsMsgSearchOp::IsInAB, "is in ab"},
225     {nsMsgSearchOp::IsntInAB, "isn't in ab"},
226     {nsMsgSearchOp::IsGreaterThan, "is greater than"},
227     {nsMsgSearchOp::IsLessThan, "is less than"},
228     {nsMsgSearchOp::Matches, "matches"},
229     {nsMsgSearchOp::DoesntMatch, "doesn't match"}};
230 
231 static const unsigned int sNumSearchOperatorEntryTable =
232     MOZ_ARRAY_LENGTH(SearchOperatorEntryTable);
233 
NS_MsgGetOperatorFromString(const char * string,int16_t * op)234 nsresult NS_MsgGetOperatorFromString(const char* string, int16_t* op) {
235   NS_ENSURE_ARG_POINTER(string);
236   NS_ENSURE_ARG_POINTER(op);
237 
238   bool found = false;
239   for (unsigned int idxOp = 0; idxOp < sNumSearchOperatorEntryTable; idxOp++) {
240     // I'm using the idx's as aliases into MSG_SearchAttribute and
241     // MSG_SearchOperator enums which is legal because of the way the
242     // enums are defined (starts at 0, numItems at end)
243     if (!PL_strcasecmp(string, SearchOperatorEntryTable[idxOp].opName)) {
244       found = true;
245       *op = SearchOperatorEntryTable[idxOp].op;
246       break;
247     }
248   }
249   return (found) ? NS_OK : NS_ERROR_INVALID_ARG;
250 }
251 
NS_MsgGetStringForOperator(int16_t op,const char ** string)252 nsresult NS_MsgGetStringForOperator(int16_t op, const char** string) {
253   NS_ENSURE_ARG_POINTER(string);
254 
255   bool found = false;
256   for (unsigned int idxOp = 0; idxOp < sNumSearchOperatorEntryTable; idxOp++) {
257     // I'm using the idx's as aliases into MSG_SearchAttribute and
258     // MSG_SearchOperator enums which is legal because of the way the
259     // enums are defined (starts at 0, numItems at end)
260     if (op == SearchOperatorEntryTable[idxOp].op) {
261       found = true;
262       *string = SearchOperatorEntryTable[idxOp].opName;
263       break;
264     }
265   }
266 
267   return (found) ? NS_OK : NS_ERROR_INVALID_ARG;
268 }
269 
NS_MsgGetUntranslatedStatusName(uint32_t s,nsCString * outName)270 void NS_MsgGetUntranslatedStatusName(uint32_t s, nsCString* outName) {
271   const char* tmpOutName = NULL;
272 #define MSG_STATUS_MASK                                    \
273   (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |  \
274    nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::New | \
275    nsMsgMessageFlags::Marked)
276   uint32_t maskOut = (s & MSG_STATUS_MASK);
277 
278   // diddle the flags to pay attention to the most important ones first, if
279   // multiple flags are set. Should remove this code from the winfe.
280   if (maskOut & nsMsgMessageFlags::New) maskOut = nsMsgMessageFlags::New;
281   if (maskOut & nsMsgMessageFlags::Replied &&
282       maskOut & nsMsgMessageFlags::Forwarded)
283     maskOut = nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded;
284   else if (maskOut & nsMsgMessageFlags::Forwarded)
285     maskOut = nsMsgMessageFlags::Forwarded;
286   else if (maskOut & nsMsgMessageFlags::Replied)
287     maskOut = nsMsgMessageFlags::Replied;
288 
289   switch (maskOut) {
290     case nsMsgMessageFlags::Read:
291       tmpOutName = "read";
292       break;
293     case nsMsgMessageFlags::Replied:
294       tmpOutName = "replied";
295       break;
296     case nsMsgMessageFlags::Forwarded:
297       tmpOutName = "forwarded";
298       break;
299     case nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied:
300       tmpOutName = "replied and forwarded";
301       break;
302     case nsMsgMessageFlags::New:
303       tmpOutName = "new";
304       break;
305     case nsMsgMessageFlags::Marked:
306       tmpOutName = "flagged";
307       break;
308     default:
309       // This is fine, status may be "unread" for example
310       break;
311   }
312 
313   if (tmpOutName) *outName = tmpOutName;
314 }
315 
NS_MsgGetStatusValueFromName(char * name)316 int32_t NS_MsgGetStatusValueFromName(char* name) {
317   if (!strcmp("read", name)) return nsMsgMessageFlags::Read;
318   if (!strcmp("replied", name)) return nsMsgMessageFlags::Replied;
319   if (!strcmp("forwarded", name)) return nsMsgMessageFlags::Forwarded;
320   if (!strcmp("replied and forwarded", name))
321     return nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied;
322   if (!strcmp("new", name)) return nsMsgMessageFlags::New;
323   if (!strcmp("flagged", name)) return nsMsgMessageFlags::Marked;
324   return 0;
325 }
326 
327 // Needed for DeStream method.
nsMsgSearchTerm()328 nsMsgSearchTerm::nsMsgSearchTerm() {
329   // initialize this to zero
330   m_value.attribute = 0;
331   m_value.u.priority = 0;
332   m_attribute = nsMsgSearchAttrib::Default;
333   m_operator = nsMsgSearchOp::Contains;
334   mBeginsGrouping = false;
335   mEndsGrouping = false;
336   m_matchAll = false;
337 
338   // valgrind warning during GC/java data check suggests
339   // m_booleanp needs to be initialized too.
340   m_booleanOp = nsMsgSearchBooleanOp::BooleanAND;
341 }
342 
nsMsgSearchTerm(nsMsgSearchAttribValue attrib,nsMsgSearchOpValue op,nsIMsgSearchValue * val,nsMsgSearchBooleanOperator boolOp,const char * aCustomString)343 nsMsgSearchTerm::nsMsgSearchTerm(nsMsgSearchAttribValue attrib,
344                                  nsMsgSearchOpValue op, nsIMsgSearchValue* val,
345                                  nsMsgSearchBooleanOperator boolOp,
346                                  const char* aCustomString) {
347   m_operator = op;
348   m_attribute = attrib;
349   m_booleanOp = boolOp;
350   if (attrib > nsMsgSearchAttrib::OtherHeader &&
351       attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes && aCustomString) {
352     m_arbitraryHeader = aCustomString;
353     ToLowerCaseExceptSpecials(m_arbitraryHeader);
354   } else if (attrib == nsMsgSearchAttrib::Custom) {
355     m_customId = aCustomString;
356   }
357 
358   nsMsgResultElement::AssignValues(val, &m_value);
359   m_matchAll = false;
360 }
361 
~nsMsgSearchTerm()362 nsMsgSearchTerm::~nsMsgSearchTerm() {}
363 
NS_IMPL_ISUPPORTS(nsMsgSearchTerm,nsIMsgSearchTerm)364 NS_IMPL_ISUPPORTS(nsMsgSearchTerm, nsIMsgSearchTerm)
365 
366 // Perhaps we could find a better place for this?
367 // Caller needs to free.
368 /* static */ char* nsMsgSearchTerm::EscapeQuotesInStr(const char* str) {
369   int numQuotes = 0;
370   for (const char* strPtr = str; *strPtr; strPtr++)
371     if (*strPtr == '"') numQuotes++;
372   int escapedStrLen = PL_strlen(str) + numQuotes;
373   char* escapedStr = (char*)PR_Malloc(escapedStrLen + 1);
374   if (escapedStr) {
375     char* destPtr;
376     for (destPtr = escapedStr; *str; str++) {
377       if (*str == '"') *destPtr++ = '\\';
378       *destPtr++ = *str;
379     }
380     *destPtr = '\0';
381   }
382   return escapedStr;
383 }
384 
OutputValue(nsCString & outputStr)385 nsresult nsMsgSearchTerm::OutputValue(nsCString& outputStr) {
386   if (IS_STRING_ATTRIBUTE(m_attribute) && !m_value.utf8String.IsEmpty()) {
387     bool quoteVal = false;
388     // need to quote strings with ')' and strings starting with '"' or ' '
389     // filter code will escape quotes
390     if (m_value.utf8String.FindChar(')') != kNotFound ||
391         (m_value.utf8String.First() == ' ') ||
392         (m_value.utf8String.First() == '"')) {
393       quoteVal = true;
394       outputStr += "\"";
395     }
396     if (m_value.utf8String.FindChar('"') != kNotFound) {
397       char* escapedString =
398           nsMsgSearchTerm::EscapeQuotesInStr(m_value.utf8String.get());
399       if (escapedString) {
400         outputStr += escapedString;
401         PR_Free(escapedString);
402       }
403 
404     } else {
405       outputStr += m_value.utf8String;
406     }
407     if (quoteVal) outputStr += "\"";
408   } else {
409     switch (m_attribute) {
410       case nsMsgSearchAttrib::Date: {
411         PRExplodedTime exploded;
412         PR_ExplodeTime(m_value.u.date, PR_LocalTimeParameters, &exploded);
413 
414         // wow, so tm_mon is 0 based, tm_mday is 1 based.
415         char dateBuf[100];
416         PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded);
417         outputStr += dateBuf;
418         break;
419       }
420       case nsMsgSearchAttrib::AgeInDays: {
421         outputStr.AppendInt(m_value.u.age);
422         break;
423       }
424       case nsMsgSearchAttrib::Label: {
425         outputStr.AppendInt(m_value.u.label);
426         break;
427       }
428       case nsMsgSearchAttrib::JunkStatus: {
429         outputStr.AppendInt(
430             m_value.u.junkStatus);  // only if we write to disk, right?
431         break;
432       }
433       case nsMsgSearchAttrib::JunkPercent: {
434         outputStr.AppendInt(m_value.u.junkPercent);
435         break;
436       }
437       case nsMsgSearchAttrib::MsgStatus: {
438         nsAutoCString status;
439         NS_MsgGetUntranslatedStatusName(m_value.u.msgStatus, &status);
440         outputStr += status;
441         break;
442       }
443       case nsMsgSearchAttrib::Priority: {
444         nsAutoCString priority;
445         NS_MsgGetUntranslatedPriorityName(m_value.u.priority, priority);
446         outputStr += priority;
447         break;
448       }
449       case nsMsgSearchAttrib::HasAttachmentStatus: {
450         outputStr.AppendLiteral("true");  // don't need anything here, really
451         break;
452       }
453       case nsMsgSearchAttrib::Size: {
454         outputStr.AppendInt(m_value.u.size);
455         break;
456       }
457       case nsMsgSearchAttrib::Uint32HdrProperty: {
458         outputStr.AppendInt(m_value.u.msgStatus);
459         break;
460       }
461       default:
462         NS_ASSERTION(false, "trying to output invalid attribute");
463         break;
464     }
465   }
466   return NS_OK;
467 }
468 
GetTermAsString(nsACString & outStream)469 NS_IMETHODIMP nsMsgSearchTerm::GetTermAsString(nsACString& outStream) {
470   const char* operatorStr;
471   nsAutoCString outputStr;
472   nsresult rv;
473 
474   if (m_matchAll) {
475     outStream = "ALL";
476     return NS_OK;
477   }
478 
479   // if arbitrary header, use it instead!
480   if (m_attribute > nsMsgSearchAttrib::OtherHeader &&
481       m_attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes) {
482     outputStr = "\"";
483     outputStr += m_arbitraryHeader;
484     outputStr += "\"";
485   } else if (m_attribute == nsMsgSearchAttrib::Custom) {
486     // use the custom id as the string
487     outputStr = m_customId;
488   }
489 
490   else if (m_attribute == nsMsgSearchAttrib::Uint32HdrProperty) {
491     outputStr = m_hdrProperty;
492   } else {
493     const char* attrib;
494     rv = NS_MsgGetStringForAttribute(m_attribute, &attrib);
495     NS_ENSURE_SUCCESS(rv, rv);
496 
497     outputStr = attrib;
498   }
499 
500   outputStr += ',';
501 
502   rv = NS_MsgGetStringForOperator(m_operator, &operatorStr);
503   NS_ENSURE_SUCCESS(rv, rv);
504 
505   outputStr += operatorStr;
506   outputStr += ',';
507 
508   OutputValue(outputStr);
509   outStream = outputStr;
510   return NS_OK;
511 }
512 
513 // fill in m_value from the input stream.
ParseValue(char * inStream)514 nsresult nsMsgSearchTerm::ParseValue(char* inStream) {
515   if (IS_STRING_ATTRIBUTE(m_attribute)) {
516     bool quoteVal = false;
517     while (isspace(*inStream)) inStream++;
518     // need to remove pair of '"', if present
519     if (*inStream == '"') {
520       quoteVal = true;
521       inStream++;
522     }
523     int valueLen = PL_strlen(inStream);
524     if (quoteVal && inStream[valueLen - 1] == '"') valueLen--;
525 
526     m_value.utf8String.Assign(inStream, valueLen);
527     CopyUTF8toUTF16(m_value.utf8String, m_value.utf16String);
528   } else {
529     switch (m_attribute) {
530       case nsMsgSearchAttrib::Date:
531         PR_ParseTimeString(inStream, false, &m_value.u.date);
532         break;
533       case nsMsgSearchAttrib::MsgStatus:
534         m_value.u.msgStatus = NS_MsgGetStatusValueFromName(inStream);
535         break;
536       case nsMsgSearchAttrib::Priority:
537         NS_MsgGetPriorityFromString(inStream, m_value.u.priority);
538         break;
539       case nsMsgSearchAttrib::AgeInDays:
540         m_value.u.age = atoi(inStream);
541         break;
542       case nsMsgSearchAttrib::Label:
543         m_value.u.label = atoi(inStream);
544         break;
545       case nsMsgSearchAttrib::JunkStatus:
546         m_value.u.junkStatus =
547             atoi(inStream);  // only if we read from disk, right?
548         break;
549       case nsMsgSearchAttrib::JunkPercent:
550         m_value.u.junkPercent = atoi(inStream);
551         break;
552       case nsMsgSearchAttrib::HasAttachmentStatus:
553         m_value.u.msgStatus = nsMsgMessageFlags::Attachment;
554         break;  // this should always be true.
555       case nsMsgSearchAttrib::Size:
556         m_value.u.size = atoi(inStream);
557         break;
558       default:
559         NS_ASSERTION(false, "invalid attribute parsing search term value");
560         break;
561     }
562   }
563   m_value.attribute = m_attribute;
564   return NS_OK;
565 }
566 
567 // find the operator code for this operator string.
ParseOperator(char * inStream,nsMsgSearchOpValue * value)568 nsresult nsMsgSearchTerm::ParseOperator(char* inStream,
569                                         nsMsgSearchOpValue* value) {
570   NS_ENSURE_ARG_POINTER(value);
571   int16_t operatorVal;
572   while (isspace(*inStream)) inStream++;
573 
574   char* commaSep = PL_strchr(inStream, ',');
575 
576   if (commaSep) *commaSep = '\0';
577 
578   nsresult rv = NS_MsgGetOperatorFromString(inStream, &operatorVal);
579   if (NS_SUCCEEDED(rv)) *value = (nsMsgSearchOpValue)operatorVal;
580   return rv;
581 }
582 
583 // find the attribute code for this comma-delimited attribute.
ParseAttribute(char * inStream,nsMsgSearchAttribValue * attrib)584 nsresult nsMsgSearchTerm::ParseAttribute(char* inStream,
585                                          nsMsgSearchAttribValue* attrib) {
586   while (isspace(*inStream)) inStream++;
587 
588   // if we are dealing with an arbitrary header, it will be quoted....
589   // it seems like a kludge, but to distinguish arbitrary headers from
590   // standard headers with the same name, like "Date", we'll use the
591   // presence of the quote to recognize arbitrary headers. We leave the
592   // leading quote as a flag, but remove the trailing quote.
593   bool quoteVal = false;
594   if (*inStream == '"') quoteVal = true;
595 
596   // arbitrary headers are quoted. Skip first character, which will be the
597   // first quote for arbitrary headers
598   char* separator = strchr(inStream + 1, quoteVal ? '"' : ',');
599 
600   if (separator) *separator = '\0';
601 
602   nsAutoCString customId;
603   nsresult rv = NS_MsgGetAttributeFromString(inStream, attrib, m_customId);
604   NS_ENSURE_SUCCESS(rv, rv);
605 
606   if (*attrib > nsMsgSearchAttrib::OtherHeader &&
607       *attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) {
608     // We are dealing with an arbitrary header.
609     m_arbitraryHeader = inStream + 1;  // remove the leading quote
610     ToLowerCaseExceptSpecials(m_arbitraryHeader);
611   }
612   return rv;
613 }
614 
615 // De stream one search term. If the condition looks like
616 // condition = "(to or cc, contains, r-thompson) AND (body, doesn't contain,
617 // fred)" This routine should get called twice, the first time with "to or cc,
618 // contains, r-thompson", the second time with "body, doesn't contain, fred"
619 
DeStreamNew(char * inStream,int16_t)620 nsresult nsMsgSearchTerm::DeStreamNew(char* inStream, int16_t /*length*/) {
621   if (!strcmp(inStream, "ALL")) {
622     m_matchAll = true;
623     return NS_OK;
624   }
625   char* commaSep = PL_strchr(inStream, ',');
626   nsresult rv = ParseAttribute(
627       inStream,
628       &m_attribute);  // will allocate space for arbitrary header if necessary
629   NS_ENSURE_SUCCESS(rv, rv);
630   if (!commaSep) return NS_ERROR_INVALID_ARG;
631   char* secondCommaSep = PL_strchr(commaSep + 1, ',');
632   if (commaSep) rv = ParseOperator(commaSep + 1, &m_operator);
633   NS_ENSURE_SUCCESS(rv, rv);
634   // convert label filters and saved searches to keyword equivalents
635   if (secondCommaSep) ParseValue(secondCommaSep + 1);
636   if (m_attribute == nsMsgSearchAttrib::Label) {
637     nsAutoCString keyword("$label");
638     m_value.attribute = m_attribute = nsMsgSearchAttrib::Keywords;
639     keyword.Append('0' + m_value.u.label);
640     m_value.utf8String = keyword;
641     CopyUTF8toUTF16(keyword, m_value.utf16String);
642   }
643   return NS_OK;
644 }
645 
646 // Looks in the MessageDB for the user specified arbitrary header, if it finds
647 // the header, it then looks for a match against the value for the header.
MatchArbitraryHeader(nsIMsgSearchScopeTerm * scope,uint32_t length,const char * charset,bool charsetOverride,nsIMsgDBHdr * msg,nsIMsgDatabase * db,const nsACString & headers,bool ForFiltering,bool * pResult)648 nsresult nsMsgSearchTerm::MatchArbitraryHeader(
649     nsIMsgSearchScopeTerm* scope, uint32_t length /* in lines*/,
650     const char* charset, bool charsetOverride, nsIMsgDBHdr* msg,
651     nsIMsgDatabase* db, const nsACString& headers, bool ForFiltering,
652     bool* pResult) {
653   NS_ENSURE_ARG_POINTER(pResult);
654 
655   *pResult = false;
656   nsresult rv = NS_OK;
657   bool matchExpected = m_operator == nsMsgSearchOp::Contains ||
658                        m_operator == nsMsgSearchOp::Is ||
659                        m_operator == nsMsgSearchOp::BeginsWith ||
660                        m_operator == nsMsgSearchOp::EndsWith;
661   // Initialize result to what we want if we don't find the header at all.
662   bool result = !matchExpected;
663 
664   nsCString dbHdrValue;
665   msg->GetStringProperty(m_arbitraryHeader.get(), getter_Copies(dbHdrValue));
666   if (!dbHdrValue.IsEmpty()) {
667     // Match value with the other info. It doesn't check all header occurrences,
668     // so we use it only if we match and do line by line headers parsing
669     // otherwise.
670     rv = MatchRfc2047String(dbHdrValue, charset, charsetOverride, pResult);
671     if (matchExpected == *pResult) return rv;
672 
673     // Preset result in case we don't have access to the headers, like for IMAP.
674     result = *pResult;
675   }
676 
677   nsMsgBodyHandler* bodyHandler =
678       new nsMsgBodyHandler(scope, length, msg, db, headers.BeginReading(),
679                            headers.Length(), ForFiltering);
680   bodyHandler->SetStripHeaders(false);
681 
682   nsCString headerFullValue;  // Contains matched header value accumulated over
683                               // multiple lines.
684   nsAutoCString buf;
685   nsAutoCString curMsgHeader;
686   bool processingHeaders = true;
687 
688   while (processingHeaders) {
689     nsCString charsetIgnored;
690     if (bodyHandler->GetNextLine(buf, charsetIgnored) < 0 ||
691         EMPTY_MESSAGE_LINE(buf))
692       processingHeaders =
693           false;  // No more lines or empty line terminating headers.
694 
695     bool isContinuationHeader =
696         processingHeaders ? NS_IsAsciiWhitespace(buf.CharAt(0)) : false;
697 
698     // If we're not on a continuation header the header value is not empty,
699     // we have finished accumulating the header value by iterating over all
700     // header lines. Now we need to check whether the value is a match.
701     if (!isContinuationHeader && !headerFullValue.IsEmpty()) {
702       bool stringMatches;
703       // Match value with the other info.
704       rv = MatchRfc2047String(headerFullValue, charset, charsetOverride,
705                               &stringMatches);
706       if (matchExpected == stringMatches)  // if we found a match
707       {
708         // If we found a match, stop examining the headers.
709         processingHeaders = false;
710         result = stringMatches;
711       }
712       // Prepare for repeated header of the same type.
713       headerFullValue.Truncate();
714     }
715 
716     // We got result or finished processing all lines.
717     if (!processingHeaders) break;
718 
719     char* buf_end = (char*)(buf.get() + buf.Length());
720     int headerLength = m_arbitraryHeader.Length();
721 
722     // If the line starts with whitespace, then we use the current header.
723     if (!isContinuationHeader) {
724       // Here we start a new header.
725       uint32_t colonPos = buf.FindChar(':');
726       curMsgHeader = StringHead(buf, colonPos);
727     }
728 
729     if (curMsgHeader.Equals(m_arbitraryHeader,
730                             nsCaseInsensitiveCStringComparator)) {
731       // Process the value:
732       // Value occurs after the header name or whitespace continuation char.
733       const char* headerValue =
734           buf.get() + (isContinuationHeader ? 1 : headerLength);
735       if (headerValue < buf_end &&
736           headerValue[0] ==
737               ':')  // + 1 to account for the colon which is MANDATORY
738         headerValue++;
739 
740       // Strip leading white space.
741       while (headerValue < buf_end && isspace(*headerValue)) headerValue++;
742 
743       // Strip trailing white space.
744       char* end = buf_end - 1;
745       while (headerValue < end && isspace(*end)) {
746         *end = '\0';
747         end--;
748       }
749 
750       // Any continuation whitespace is converted to a single space.
751       if (!headerFullValue.IsEmpty()) headerFullValue.Append(' ');
752       headerFullValue.Append(nsDependentCString(headerValue));
753     }
754   }
755 
756   delete bodyHandler;
757   *pResult = result;
758   return rv;
759 }
760 
MatchHdrProperty(nsIMsgDBHdr * aHdr,bool * aResult)761 NS_IMETHODIMP nsMsgSearchTerm::MatchHdrProperty(nsIMsgDBHdr* aHdr,
762                                                 bool* aResult) {
763   NS_ENSURE_ARG_POINTER(aResult);
764   NS_ENSURE_ARG_POINTER(aHdr);
765 
766   nsCString dbHdrValue;
767   aHdr->GetStringProperty(m_hdrProperty.get(), getter_Copies(dbHdrValue));
768   return MatchString(dbHdrValue, nullptr, aResult);
769 }
770 
MatchFolderFlag(nsIMsgDBHdr * aMsgToMatch,bool * aResult)771 NS_IMETHODIMP nsMsgSearchTerm::MatchFolderFlag(nsIMsgDBHdr* aMsgToMatch,
772                                                bool* aResult) {
773   NS_ENSURE_ARG_POINTER(aMsgToMatch);
774   NS_ENSURE_ARG_POINTER(aResult);
775 
776   nsCOMPtr<nsIMsgFolder> msgFolder;
777   nsresult rv = aMsgToMatch->GetFolder(getter_AddRefs(msgFolder));
778   NS_ENSURE_SUCCESS(rv, rv);
779   uint32_t folderFlags;
780   msgFolder->GetFlags(&folderFlags);
781   return MatchStatus(folderFlags, aResult);
782 }
783 
MatchUint32HdrProperty(nsIMsgDBHdr * aHdr,bool * aResult)784 NS_IMETHODIMP nsMsgSearchTerm::MatchUint32HdrProperty(nsIMsgDBHdr* aHdr,
785                                                       bool* aResult) {
786   NS_ENSURE_ARG_POINTER(aResult);
787   NS_ENSURE_ARG_POINTER(aHdr);
788 
789   nsresult rv = NS_OK;
790   uint32_t dbHdrValue;
791   aHdr->GetUint32Property(m_hdrProperty.get(), &dbHdrValue);
792 
793   bool result = false;
794   switch (m_operator) {
795     case nsMsgSearchOp::IsGreaterThan:
796       if (dbHdrValue > m_value.u.msgStatus) result = true;
797       break;
798     case nsMsgSearchOp::IsLessThan:
799       if (dbHdrValue < m_value.u.msgStatus) result = true;
800       break;
801     case nsMsgSearchOp::Is:
802       if (dbHdrValue == m_value.u.msgStatus) result = true;
803       break;
804     case nsMsgSearchOp::Isnt:
805       if (dbHdrValue != m_value.u.msgStatus) result = true;
806       break;
807     default:
808       rv = NS_ERROR_FAILURE;
809       NS_ERROR("invalid compare op for uint");
810   }
811   *aResult = result;
812   return rv;
813 }
814 
MatchBody(nsIMsgSearchScopeTerm * scope,uint64_t offset,uint32_t length,const char * folderCharset,nsIMsgDBHdr * msg,nsIMsgDatabase * db,bool * pResult)815 nsresult nsMsgSearchTerm::MatchBody(nsIMsgSearchScopeTerm* scope,
816                                     uint64_t offset,
817                                     uint32_t length /*in lines*/,
818                                     const char* folderCharset, nsIMsgDBHdr* msg,
819                                     nsIMsgDatabase* db, bool* pResult) {
820   NS_ENSURE_ARG_POINTER(pResult);
821 
822   nsresult rv = NS_OK;
823 
824   bool result = false;
825   *pResult = false;
826 
827   // Small hack so we don't look all through a message when someone has
828   // specified "BODY IS foo". ### Since length is in lines, this is not quite
829   // right.
830   if ((length > 0) &&
831       (m_operator == nsMsgSearchOp::Is || m_operator == nsMsgSearchOp::Isnt))
832     length = m_value.utf8String.Length();
833 
834   nsMsgBodyHandler* bodyHan = new nsMsgBodyHandler(scope, length, msg, db);
835   if (!bodyHan) return NS_ERROR_OUT_OF_MEMORY;
836 
837   nsAutoCString buf;
838   bool endOfFile = false;  // if retValue == 0, we've hit the end of the file
839   uint32_t lines = 0;
840 
841   // Change the sense of the loop so we don't bail out prematurely
842   // on negative terms. i.e. opDoesntContain must look at all lines
843   bool boolContinueLoop;
844   GetMatchAllBeforeDeciding(&boolContinueLoop);
845   result = boolContinueLoop;
846 
847   nsCString compare;
848   nsCString charset;
849   while (!endOfFile && result == boolContinueLoop) {
850     if (bodyHan->GetNextLine(buf, charset) >= 0) {
851       bool softLineBreak = false;
852       // Do in-place decoding of quoted printable
853       if (bodyHan->IsQP()) {
854         softLineBreak = StringEndsWith(buf, "="_ns);
855         MsgStripQuotedPrintable(buf);
856         // If soft line break, chop off the last char as well.
857         size_t bufLength = buf.Length();
858         if ((bufLength > 0) && softLineBreak) buf.SetLength(bufLength - 1);
859       }
860       compare.Append(buf);
861       // If this line ends with a soft line break, loop around
862       // and get the next line before looking for the search string.
863       // This assumes the message can't end on a QP soft line break.
864       // That seems like a pretty safe assumption.
865       if (softLineBreak) continue;
866       if (!compare.IsEmpty()) {
867         char startChar = (char)compare.CharAt(0);
868         if (startChar != '\r' && startChar != '\n') {
869           rv = MatchString(compare,
870                            charset.IsEmpty() ? folderCharset : charset.get(),
871                            &result);
872           lines++;
873         }
874         compare.Truncate();
875       }
876     } else
877       endOfFile = true;
878   }
879 
880   delete bodyHan;
881   *pResult = result;
882   return rv;
883 }
884 
InitializeAddressBook()885 nsresult nsMsgSearchTerm::InitializeAddressBook() {
886   // the search attribute value has the URI for the address book we need to
887   // load. we need both the database and the directory.
888   nsresult rv = NS_OK;
889 
890   if (mDirectory) {
891     nsCString uri;
892     rv = mDirectory->GetURI(uri);
893     NS_ENSURE_SUCCESS(rv, rv);
894 
895     if (!uri.Equals(m_value.utf8String))
896       // clear out the directory....we are no longer pointing to the right one
897       mDirectory = nullptr;
898   }
899   if (!mDirectory) {
900     nsCOMPtr<nsIAbManager> abManager =
901         do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
902     NS_ENSURE_SUCCESS(rv, rv);
903 
904     rv =
905         abManager->GetDirectory(m_value.utf8String, getter_AddRefs(mDirectory));
906     NS_ENSURE_SUCCESS(rv, rv);
907   }
908 
909   return NS_OK;
910 }
911 
MatchInAddressBook(const nsAString & aAddress,bool * pResult)912 nsresult nsMsgSearchTerm::MatchInAddressBook(const nsAString& aAddress,
913                                              bool* pResult) {
914   nsresult rv = InitializeAddressBook();
915   *pResult = false;
916 
917   // Some junkmails have empty From: fields.
918   if (aAddress.IsEmpty()) return rv;
919 
920   if (mDirectory) {
921     nsCOMPtr<nsIAbCard> cardForAddress = nullptr;
922     rv = mDirectory->CardForEmailAddress(NS_ConvertUTF16toUTF8(aAddress),
923                                          getter_AddRefs(cardForAddress));
924     if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) return rv;
925     switch (m_operator) {
926       case nsMsgSearchOp::IsInAB:
927         if (cardForAddress) *pResult = true;
928         break;
929       case nsMsgSearchOp::IsntInAB:
930         if (!cardForAddress) *pResult = true;
931         break;
932       default:
933         rv = NS_ERROR_FAILURE;
934         NS_ERROR("invalid compare op for address book");
935     }
936   }
937 
938   return rv;
939 }
940 
941 // *pResult is false when strings don't match, true if they do.
MatchRfc2047String(const nsACString & rfc2047string,const char * charset,bool charsetOverride,bool * pResult)942 nsresult nsMsgSearchTerm::MatchRfc2047String(const nsACString& rfc2047string,
943                                              const char* charset,
944                                              bool charsetOverride,
945                                              bool* pResult) {
946   NS_ENSURE_ARG_POINTER(pResult);
947 
948   nsresult rv;
949   nsCOMPtr<nsIMimeConverter> mimeConverter =
950       do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv);
951   NS_ENSURE_SUCCESS(rv, rv);
952   nsAutoString stringToMatch;
953   rv = mimeConverter->DecodeMimeHeader(PromiseFlatCString(rfc2047string).get(),
954                                        charset, charsetOverride, false,
955                                        stringToMatch);
956   NS_ENSURE_SUCCESS(rv, rv);
957   if (m_operator == nsMsgSearchOp::IsInAB ||
958       m_operator == nsMsgSearchOp::IsntInAB)
959     return MatchInAddressBook(stringToMatch, pResult);
960 
961   return MatchString(stringToMatch, pResult);
962 }
963 
964 // *pResult is false when strings don't match, true if they do.
MatchString(const nsACString & stringToMatch,const char * charset,bool * pResult)965 nsresult nsMsgSearchTerm::MatchString(const nsACString& stringToMatch,
966                                       const char* charset, bool* pResult) {
967   NS_ENSURE_ARG_POINTER(pResult);
968 
969   bool result = false;
970 
971   nsresult rv = NS_OK;
972 
973   // Save some performance for opIsEmpty / opIsntEmpty
974   if (nsMsgSearchOp::IsEmpty == m_operator) {
975     if (stringToMatch.IsEmpty()) result = true;
976   } else if (nsMsgSearchOp::IsntEmpty == m_operator) {
977     if (!stringToMatch.IsEmpty()) result = true;
978   } else {
979     nsAutoString utf16StrToMatch;
980     rv = NS_ERROR_UNEXPECTED;
981     if (charset) {
982       rv = nsMsgI18NConvertToUnicode(nsDependentCString(charset), stringToMatch,
983                                      utf16StrToMatch);
984     }
985     if (NS_FAILED(rv)) {
986       // No charset or conversion failed, maybe due to a bad charset, try UTF-8.
987       if (mozilla::IsUtf8(stringToMatch)) {
988         CopyUTF8toUTF16(stringToMatch, utf16StrToMatch);
989       } else {
990         // Bad luck, let's assume ASCII/windows-1252 then.
991         CopyASCIItoUTF16(stringToMatch, utf16StrToMatch);
992       }
993     }
994 
995     rv = MatchString(utf16StrToMatch, &result);
996   }
997 
998   *pResult = result;
999   return rv;
1000 }
1001 
1002 // *pResult is false when strings don't match, true if they do.
MatchString(const nsAString & utf16StrToMatch,bool * pResult)1003 nsresult nsMsgSearchTerm::MatchString(const nsAString& utf16StrToMatch,
1004                                       bool* pResult) {
1005   NS_ENSURE_ARG_POINTER(pResult);
1006 
1007   bool result = false;
1008 
1009   nsresult rv = NS_OK;
1010   auto needle = m_value.utf16String;
1011 
1012   switch (m_operator) {
1013     case nsMsgSearchOp::Contains:
1014       if (CaseInsensitiveFindInReadable(needle, utf16StrToMatch)) result = true;
1015       break;
1016     case nsMsgSearchOp::DoesntContain:
1017       if (!CaseInsensitiveFindInReadable(needle, utf16StrToMatch))
1018         result = true;
1019       break;
1020     case nsMsgSearchOp::Is:
1021       if (needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator))
1022         result = true;
1023       break;
1024     case nsMsgSearchOp::Isnt:
1025       if (!needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator))
1026         result = true;
1027       break;
1028     case nsMsgSearchOp::IsEmpty:
1029       if (utf16StrToMatch.IsEmpty()) result = true;
1030       break;
1031     case nsMsgSearchOp::IsntEmpty:
1032       if (!utf16StrToMatch.IsEmpty()) result = true;
1033       break;
1034     case nsMsgSearchOp::BeginsWith:
1035       if (StringBeginsWith(utf16StrToMatch, needle,
1036                            nsCaseInsensitiveStringComparator))
1037         result = true;
1038       break;
1039     case nsMsgSearchOp::EndsWith:
1040       if (StringEndsWith(utf16StrToMatch, needle,
1041                          nsCaseInsensitiveStringComparator))
1042         result = true;
1043       break;
1044     default:
1045       rv = NS_ERROR_FAILURE;
1046       NS_ERROR("invalid compare op for matching search results");
1047   }
1048 
1049   *pResult = result;
1050   return rv;
1051 }
1052 
GetMatchAllBeforeDeciding(bool * aResult)1053 NS_IMETHODIMP nsMsgSearchTerm::GetMatchAllBeforeDeciding(bool* aResult) {
1054   *aResult = (m_operator == nsMsgSearchOp::DoesntContain ||
1055               m_operator == nsMsgSearchOp::Isnt);
1056   return NS_OK;
1057 }
1058 
MatchRfc822String(const nsACString & string,const char * charset,bool * pResult)1059 NS_IMETHODIMP nsMsgSearchTerm::MatchRfc822String(const nsACString& string,
1060                                                  const char* charset,
1061                                                  bool* pResult) {
1062   NS_ENSURE_ARG_POINTER(pResult);
1063 
1064   *pResult = false;
1065   bool result;
1066 
1067   // Change the sense of the loop so we don't bail out prematurely
1068   // on negative terms. i.e. opDoesntContain must look at all recipients
1069   bool boolContinueLoop;
1070   GetMatchAllBeforeDeciding(&boolContinueLoop);
1071   result = boolContinueLoop;
1072 
1073   // If the operator is Contains, then we can cheat and avoid having to parse
1074   // addresses. This does open up potential spurious matches for punctuation
1075   // (e.g., ; or <), but the likelihood of users intending to search for these
1076   // and also being able to match them is rather low. This optimization is not
1077   // applicable to any other search type.
1078   if (m_operator == nsMsgSearchOp::Contains)
1079     return MatchRfc2047String(string, charset, false, pResult);
1080 
1081   nsTArray<nsString> names, addresses;
1082   ExtractAllAddresses(EncodedHeader(string, charset), names, addresses);
1083   uint32_t count = names.Length();
1084 
1085   nsresult rv = NS_OK;
1086   for (uint32_t i = 0; i < count && result == boolContinueLoop; i++) {
1087     if (m_operator == nsMsgSearchOp::IsInAB ||
1088         m_operator == nsMsgSearchOp::IsntInAB) {
1089       rv = MatchInAddressBook(addresses[i], &result);
1090     } else {
1091       rv = MatchString(names[i], &result);
1092       if (boolContinueLoop == result) rv = MatchString(addresses[i], &result);
1093     }
1094   }
1095   *pResult = result;
1096   return rv;
1097 }
1098 
GetLocalTimes(PRTime a,PRTime b,PRExplodedTime & aExploded,PRExplodedTime & bExploded)1099 nsresult nsMsgSearchTerm::GetLocalTimes(PRTime a, PRTime b,
1100                                         PRExplodedTime& aExploded,
1101                                         PRExplodedTime& bExploded) {
1102   PR_ExplodeTime(a, PR_LocalTimeParameters, &aExploded);
1103   PR_ExplodeTime(b, PR_LocalTimeParameters, &bExploded);
1104   return NS_OK;
1105 }
1106 
MatchDate(PRTime dateToMatch,bool * pResult)1107 nsresult nsMsgSearchTerm::MatchDate(PRTime dateToMatch, bool* pResult) {
1108   NS_ENSURE_ARG_POINTER(pResult);
1109 
1110   nsresult rv = NS_OK;
1111   bool result = false;
1112 
1113   PRExplodedTime tmToMatch, tmThis;
1114   if (NS_SUCCEEDED(
1115           GetLocalTimes(dateToMatch, m_value.u.date, tmToMatch, tmThis))) {
1116     switch (m_operator) {
1117       case nsMsgSearchOp::IsBefore:
1118         if (tmToMatch.tm_year < tmThis.tm_year ||
1119             (tmToMatch.tm_year == tmThis.tm_year &&
1120              tmToMatch.tm_yday < tmThis.tm_yday))
1121           result = true;
1122         break;
1123       case nsMsgSearchOp::IsAfter:
1124         if (tmToMatch.tm_year > tmThis.tm_year ||
1125             (tmToMatch.tm_year == tmThis.tm_year &&
1126              tmToMatch.tm_yday > tmThis.tm_yday))
1127           result = true;
1128         break;
1129       case nsMsgSearchOp::Is:
1130         if (tmThis.tm_year == tmToMatch.tm_year &&
1131             tmThis.tm_month == tmToMatch.tm_month &&
1132             tmThis.tm_mday == tmToMatch.tm_mday)
1133           result = true;
1134         break;
1135       case nsMsgSearchOp::Isnt:
1136         if (tmThis.tm_year != tmToMatch.tm_year ||
1137             tmThis.tm_month != tmToMatch.tm_month ||
1138             tmThis.tm_mday != tmToMatch.tm_mday)
1139           result = true;
1140         break;
1141       default:
1142         rv = NS_ERROR_FAILURE;
1143         NS_ERROR("invalid compare op for dates");
1144     }
1145   }
1146   *pResult = result;
1147   return rv;
1148 }
1149 
MatchAge(PRTime msgDate,bool * pResult)1150 nsresult nsMsgSearchTerm::MatchAge(PRTime msgDate, bool* pResult) {
1151   NS_ENSURE_ARG_POINTER(pResult);
1152 
1153   bool result = false;
1154   nsresult rv = NS_OK;
1155 
1156   PRTime now = PR_Now();
1157   PRTime cutOffDay = now - m_value.u.age * PR_USEC_PER_DAY;
1158 
1159   bool cutOffDayInTheFuture = m_value.u.age < 0;
1160 
1161   // So now cutOffDay is the PRTime cut-off point.
1162   // Any msg with a time less than that will be past the age.
1163 
1164   switch (m_operator) {
1165     case nsMsgSearchOp::IsGreaterThan:  // is older than, or more in the future
1166       if ((!cutOffDayInTheFuture && msgDate < cutOffDay) ||
1167           (cutOffDayInTheFuture && msgDate > cutOffDay))
1168         result = true;
1169       break;
1170     case nsMsgSearchOp::IsLessThan:  // is younger than, or less in the future
1171       if ((!cutOffDayInTheFuture && msgDate > cutOffDay) ||
1172           (cutOffDayInTheFuture && msgDate < cutOffDay))
1173         result = true;
1174       break;
1175     case nsMsgSearchOp::Is:
1176       PRExplodedTime msgDateExploded;
1177       PRExplodedTime cutOffDayExploded;
1178       if (NS_SUCCEEDED(GetLocalTimes(msgDate, cutOffDay, msgDateExploded,
1179                                      cutOffDayExploded))) {
1180         if ((msgDateExploded.tm_mday == cutOffDayExploded.tm_mday) &&
1181             (msgDateExploded.tm_month == cutOffDayExploded.tm_month) &&
1182             (msgDateExploded.tm_year == cutOffDayExploded.tm_year))
1183           result = true;
1184       }
1185       break;
1186     default:
1187       rv = NS_ERROR_FAILURE;
1188       NS_ERROR("invalid compare op for msg age");
1189   }
1190   *pResult = result;
1191   return rv;
1192 }
1193 
MatchSize(uint32_t sizeToMatch,bool * pResult)1194 nsresult nsMsgSearchTerm::MatchSize(uint32_t sizeToMatch, bool* pResult) {
1195   NS_ENSURE_ARG_POINTER(pResult);
1196 
1197   nsresult rv = NS_OK;
1198   bool result = false;
1199   // We reduce the sizeToMatch rather than supplied size
1200   // as then we can do an exact match on the displayed value
1201   // which will be less confusing to the user.
1202   uint32_t sizeToMatchKB = sizeToMatch;
1203 
1204   if (sizeToMatchKB < 1024) sizeToMatchKB = 1024;
1205 
1206   sizeToMatchKB /= 1024;
1207 
1208   switch (m_operator) {
1209     case nsMsgSearchOp::IsGreaterThan:
1210       if (sizeToMatchKB > m_value.u.size) result = true;
1211       break;
1212     case nsMsgSearchOp::IsLessThan:
1213       if (sizeToMatchKB < m_value.u.size) result = true;
1214       break;
1215     case nsMsgSearchOp::Is:
1216       if (sizeToMatchKB == m_value.u.size) result = true;
1217       break;
1218     default:
1219       rv = NS_ERROR_FAILURE;
1220       NS_ERROR("invalid compare op for size to match");
1221   }
1222   *pResult = result;
1223   return rv;
1224 }
1225 
MatchJunkStatus(const char * aJunkScore,bool * pResult)1226 nsresult nsMsgSearchTerm::MatchJunkStatus(const char* aJunkScore,
1227                                           bool* pResult) {
1228   NS_ENSURE_ARG_POINTER(pResult);
1229 
1230   if (m_operator == nsMsgSearchOp::IsEmpty) {
1231     *pResult = !(aJunkScore && *aJunkScore);
1232     return NS_OK;
1233   }
1234   if (m_operator == nsMsgSearchOp::IsntEmpty) {
1235     *pResult = (aJunkScore && *aJunkScore);
1236     return NS_OK;
1237   }
1238 
1239   nsMsgJunkStatus junkStatus;
1240   if (aJunkScore && *aJunkScore) {
1241     junkStatus = (atoi(aJunkScore) == nsIJunkMailPlugin::IS_SPAM_SCORE)
1242                      ? nsIJunkMailPlugin::JUNK
1243                      : nsIJunkMailPlugin::GOOD;
1244   } else {
1245     // the in UI, we only show "junk" or "not junk"
1246     // unknown, or nsIJunkMailPlugin::UNCLASSIFIED is shown as not junk
1247     // so for the search to work as expected, treat unknown as not junk
1248     junkStatus = nsIJunkMailPlugin::GOOD;
1249   }
1250 
1251   nsresult rv = NS_OK;
1252   bool matches = (junkStatus == m_value.u.junkStatus);
1253 
1254   switch (m_operator) {
1255     case nsMsgSearchOp::Is:
1256       break;
1257     case nsMsgSearchOp::Isnt:
1258       matches = !matches;
1259       break;
1260     default:
1261       rv = NS_ERROR_FAILURE;
1262       matches = false;
1263       NS_ERROR("invalid compare op for junk status");
1264   }
1265 
1266   *pResult = matches;
1267   return rv;
1268 }
1269 
MatchJunkScoreOrigin(const char * aJunkScoreOrigin,bool * pResult)1270 nsresult nsMsgSearchTerm::MatchJunkScoreOrigin(const char* aJunkScoreOrigin,
1271                                                bool* pResult) {
1272   NS_ENSURE_ARG_POINTER(pResult);
1273 
1274   bool matches = false;
1275   nsresult rv = NS_OK;
1276 
1277   switch (m_operator) {
1278     case nsMsgSearchOp::Is:
1279       matches = aJunkScoreOrigin && m_value.utf8String.Equals(aJunkScoreOrigin);
1280       break;
1281     case nsMsgSearchOp::Isnt:
1282       matches =
1283           !aJunkScoreOrigin || !m_value.utf8String.Equals(aJunkScoreOrigin);
1284       break;
1285     default:
1286       rv = NS_ERROR_FAILURE;
1287       NS_ERROR("invalid compare op for junk score origin");
1288   }
1289 
1290   *pResult = matches;
1291   return rv;
1292 }
1293 
MatchJunkPercent(uint32_t aJunkPercent,bool * pResult)1294 nsresult nsMsgSearchTerm::MatchJunkPercent(uint32_t aJunkPercent,
1295                                            bool* pResult) {
1296   NS_ENSURE_ARG_POINTER(pResult);
1297 
1298   nsresult rv = NS_OK;
1299   bool result = false;
1300   switch (m_operator) {
1301     case nsMsgSearchOp::IsGreaterThan:
1302       if (aJunkPercent > m_value.u.junkPercent) result = true;
1303       break;
1304     case nsMsgSearchOp::IsLessThan:
1305       if (aJunkPercent < m_value.u.junkPercent) result = true;
1306       break;
1307     case nsMsgSearchOp::Is:
1308       if (aJunkPercent == m_value.u.junkPercent) result = true;
1309       break;
1310     default:
1311       rv = NS_ERROR_FAILURE;
1312       NS_ERROR("invalid compare op for junk percent");
1313   }
1314   *pResult = result;
1315   return rv;
1316 }
1317 
MatchLabel(nsMsgLabelValue aLabelValue,bool * pResult)1318 nsresult nsMsgSearchTerm::MatchLabel(nsMsgLabelValue aLabelValue,
1319                                      bool* pResult) {
1320   NS_ENSURE_ARG_POINTER(pResult);
1321 
1322   nsresult rv = NS_OK;
1323   bool result = false;
1324   switch (m_operator) {
1325     case nsMsgSearchOp::Is:
1326       if (m_value.u.label == aLabelValue) result = true;
1327       break;
1328     case nsMsgSearchOp::Isnt:
1329       if (m_value.u.label != aLabelValue) result = true;
1330       break;
1331     default:
1332       rv = NS_ERROR_FAILURE;
1333       NS_ERROR("invalid compare op for label value");
1334   }
1335 
1336   *pResult = result;
1337   return rv;
1338 }
1339 
1340 // MatchStatus () is not only used for nsMsgMessageFlags but also for
1341 // nsMsgFolderFlags (both being 'unsigned long')
MatchStatus(uint32_t statusToMatch,bool * pResult)1342 nsresult nsMsgSearchTerm::MatchStatus(uint32_t statusToMatch, bool* pResult) {
1343   NS_ENSURE_ARG_POINTER(pResult);
1344 
1345   nsresult rv = NS_OK;
1346   bool matches = (statusToMatch & m_value.u.msgStatus);
1347 
1348   // nsMsgSearchOp::Is and nsMsgSearchOp::Isnt are intentionally used as
1349   // Contains and DoesntContain respectively, for legacy reasons.
1350   switch (m_operator) {
1351     case nsMsgSearchOp::Is:
1352       break;
1353     case nsMsgSearchOp::Isnt:
1354       matches = !matches;
1355       break;
1356     default:
1357       rv = NS_ERROR_FAILURE;
1358       matches = false;
1359       NS_ERROR("invalid compare op for msg status");
1360   }
1361 
1362   *pResult = matches;
1363   return rv;
1364 }
1365 
1366 /*
1367  * MatchKeyword Logic table (*pResult: + is true, - is false)
1368  *
1369  *         # Valid Tokens IsEmpty IsntEmpty Contains DoesntContain Is     Isnt
1370  *                0           +         -      -         +         -       +
1371  * Term found?                               N   Y     N   Y     N   Y   N   Y
1372  *                1           -         +    -   +     +   -     -   +   +   -
1373  *               >1           -         +    -   +     +   -     -   -   +   +
1374  */
1375 // look up nsMsgSearchTerm::m_value in space-delimited keywordList
MatchKeyword(const nsACString & keywordList,bool * pResult)1376 nsresult nsMsgSearchTerm::MatchKeyword(const nsACString& keywordList,
1377                                        bool* pResult) {
1378   NS_ENSURE_ARG_POINTER(pResult);
1379 
1380   bool matches = false;
1381 
1382   // special-case empty for performance reasons
1383   if (keywordList.IsEmpty()) {
1384     *pResult = m_operator != nsMsgSearchOp::Contains &&
1385                m_operator != nsMsgSearchOp::Is &&
1386                m_operator != nsMsgSearchOp::IsntEmpty;
1387     return NS_OK;
1388   }
1389 
1390   // check if we can skip expensive valid keywordList test
1391   if (m_operator == nsMsgSearchOp::DoesntContain ||
1392       m_operator == nsMsgSearchOp::Contains) {
1393     nsCString keywordString(keywordList);
1394     const uint32_t kKeywordLen = m_value.utf8String.Length();
1395     const char* matchStart =
1396         PL_strstr(keywordString.get(), m_value.utf8String.get());
1397     while (matchStart) {
1398       // For a real match, matchStart must be the start of the keywordList or
1399       // preceded by a space and matchEnd must point to a \0 or space.
1400       const char* matchEnd = matchStart + kKeywordLen;
1401       if ((matchStart == keywordString.get() || matchStart[-1] == ' ') &&
1402           (!*matchEnd || *matchEnd == ' ')) {
1403         // found the keyword
1404         *pResult = m_operator == nsMsgSearchOp::Contains;
1405         return NS_OK;
1406       }
1407       // no match yet, so search on
1408       matchStart = PL_strstr(matchEnd, m_value.utf8String.get());
1409     }
1410     // keyword not found
1411     *pResult = m_operator == nsMsgSearchOp::DoesntContain;
1412     return NS_OK;
1413   }
1414 
1415   // Only accept valid keys in tokens.
1416   nsresult rv = NS_OK;
1417   nsTArray<nsCString> keywordArray;
1418   ParseString(keywordList, ' ', keywordArray);
1419   nsCOMPtr<nsIMsgTagService> tagService(
1420       do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv));
1421   NS_ENSURE_SUCCESS(rv, rv);
1422 
1423   // Loop through tokens in keywords
1424   uint32_t count = keywordArray.Length();
1425   for (uint32_t i = 0; i < count; i++) {
1426     // is this token a valid tag? Otherwise ignore it
1427     bool isValid;
1428     rv = tagService->IsValidKey(keywordArray[i], &isValid);
1429     NS_ENSURE_SUCCESS(rv, rv);
1430 
1431     if (isValid) {
1432       // IsEmpty fails on any valid token
1433       if (m_operator == nsMsgSearchOp::IsEmpty) {
1434         *pResult = false;
1435         return rv;
1436       }
1437 
1438       // IsntEmpty succeeds on any valid token
1439       if (m_operator == nsMsgSearchOp::IsntEmpty) {
1440         *pResult = true;
1441         return rv;
1442       }
1443 
1444       // Does this valid tag key match our search term?
1445       matches = keywordArray[i].Equals(m_value.utf8String);
1446 
1447       // Is or Isn't partly determined on a single unmatched token
1448       if (!matches) {
1449         if (m_operator == nsMsgSearchOp::Is) {
1450           *pResult = false;
1451           return rv;
1452         }
1453         if (m_operator == nsMsgSearchOp::Isnt) {
1454           *pResult = true;
1455           return rv;
1456         }
1457       }
1458     }
1459   }
1460 
1461   if (m_operator == nsMsgSearchOp::Is) {
1462     *pResult = matches;
1463     return NS_OK;
1464   }
1465 
1466   if (m_operator == nsMsgSearchOp::Isnt) {
1467     *pResult = !matches;
1468     return NS_OK;
1469   }
1470 
1471   if (m_operator == nsMsgSearchOp::IsEmpty) {
1472     *pResult = true;
1473     return NS_OK;
1474   }
1475 
1476   if (m_operator == nsMsgSearchOp::IsntEmpty) {
1477     *pResult = false;
1478     return NS_OK;
1479   }
1480 
1481   // no valid match operator found
1482   *pResult = false;
1483   NS_ERROR("invalid compare op for msg status");
1484   return NS_ERROR_FAILURE;
1485 }
1486 
MatchPriority(nsMsgPriorityValue priorityToMatch,bool * pResult)1487 nsresult nsMsgSearchTerm::MatchPriority(nsMsgPriorityValue priorityToMatch,
1488                                         bool* pResult) {
1489   NS_ENSURE_ARG_POINTER(pResult);
1490 
1491   nsresult rv = NS_OK;
1492   bool result = false;
1493 
1494   // Use this ugly little hack to get around the fact that enums don't have
1495   // integer compare operators
1496   int p1 = (priorityToMatch == nsMsgPriority::none) ? (int)nsMsgPriority::normal
1497                                                     : (int)priorityToMatch;
1498   int p2 = (int)m_value.u.priority;
1499 
1500   switch (m_operator) {
1501     case nsMsgSearchOp::IsHigherThan:
1502       if (p1 > p2) result = true;
1503       break;
1504     case nsMsgSearchOp::IsLowerThan:
1505       if (p1 < p2) result = true;
1506       break;
1507     case nsMsgSearchOp::Is:
1508       if (p1 == p2) result = true;
1509       break;
1510     case nsMsgSearchOp::Isnt:
1511       if (p1 != p2) result = true;
1512       break;
1513     default:
1514       rv = NS_ERROR_FAILURE;
1515       NS_ERROR("invalid compare op for priority");
1516   }
1517   *pResult = result;
1518   return rv;
1519 }
1520 
1521 // match a custom search term
MatchCustom(nsIMsgDBHdr * aHdr,bool * pResult)1522 NS_IMETHODIMP nsMsgSearchTerm::MatchCustom(nsIMsgDBHdr* aHdr, bool* pResult) {
1523   NS_ENSURE_ARG_POINTER(pResult);
1524 
1525   nsresult rv;
1526   nsCOMPtr<nsIMsgFilterService> filterService =
1527       do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
1528   NS_ENSURE_SUCCESS(rv, rv);
1529 
1530   nsCOMPtr<nsIMsgSearchCustomTerm> customTerm;
1531   rv = filterService->GetCustomTerm(m_customId, getter_AddRefs(customTerm));
1532   NS_ENSURE_SUCCESS(rv, rv);
1533 
1534   if (customTerm)
1535     return customTerm->Match(aHdr, m_value.utf8String, m_operator, pResult);
1536   *pResult = false;         // default to no match if term is missing
1537   return NS_ERROR_FAILURE;  // missing custom term
1538 }
1539 
1540 // set the id of a custom search term
SetCustomId(const nsACString & aId)1541 NS_IMETHODIMP nsMsgSearchTerm::SetCustomId(const nsACString& aId) {
1542   m_customId = aId;
1543   return NS_OK;
1544 }
1545 
1546 // get the id of a custom search term
GetCustomId(nsACString & aResult)1547 NS_IMETHODIMP nsMsgSearchTerm::GetCustomId(nsACString& aResult) {
1548   aResult = m_customId;
1549   return NS_OK;
1550 }
1551 
NS_IMPL_GETSET(nsMsgSearchTerm,Attrib,nsMsgSearchAttribValue,m_attribute)1552 NS_IMPL_GETSET(nsMsgSearchTerm, Attrib, nsMsgSearchAttribValue, m_attribute)
1553 NS_IMPL_GETSET(nsMsgSearchTerm, Op, nsMsgSearchOpValue, m_operator)
1554 NS_IMPL_GETSET(nsMsgSearchTerm, MatchAll, bool, m_matchAll)
1555 
1556 NS_IMETHODIMP
1557 nsMsgSearchTerm::GetValue(nsIMsgSearchValue** aResult) {
1558   NS_ENSURE_ARG_POINTER(aResult);
1559   NS_ADDREF(*aResult = new nsMsgSearchValueImpl(&m_value));
1560   return NS_OK;
1561 }
1562 
1563 NS_IMETHODIMP
SetValue(nsIMsgSearchValue * aValue)1564 nsMsgSearchTerm::SetValue(nsIMsgSearchValue* aValue) {
1565   nsMsgResultElement::AssignValues(aValue, &m_value);
1566   return NS_OK;
1567 }
1568 
1569 NS_IMETHODIMP
GetBooleanAnd(bool * aResult)1570 nsMsgSearchTerm::GetBooleanAnd(bool* aResult) {
1571   NS_ENSURE_ARG_POINTER(aResult);
1572   *aResult = (m_booleanOp == nsMsgSearchBooleanOp::BooleanAND);
1573   return NS_OK;
1574 }
1575 
1576 NS_IMETHODIMP
SetBooleanAnd(bool aValue)1577 nsMsgSearchTerm::SetBooleanAnd(bool aValue) {
1578   if (aValue)
1579     m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanAND);
1580   else
1581     m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanOR);
1582   return NS_OK;
1583 }
1584 
1585 NS_IMETHODIMP
GetArbitraryHeader(nsACString & aResult)1586 nsMsgSearchTerm::GetArbitraryHeader(nsACString& aResult) {
1587   aResult = m_arbitraryHeader;
1588   return NS_OK;
1589 }
1590 
1591 NS_IMETHODIMP
SetArbitraryHeader(const nsACString & aValue)1592 nsMsgSearchTerm::SetArbitraryHeader(const nsACString& aValue) {
1593   m_arbitraryHeader = aValue;
1594   ToLowerCaseExceptSpecials(m_arbitraryHeader);
1595   return NS_OK;
1596 }
1597 
1598 NS_IMETHODIMP
GetHdrProperty(nsACString & aResult)1599 nsMsgSearchTerm::GetHdrProperty(nsACString& aResult) {
1600   aResult = m_hdrProperty;
1601   return NS_OK;
1602 }
1603 
1604 NS_IMETHODIMP
SetHdrProperty(const nsACString & aValue)1605 nsMsgSearchTerm::SetHdrProperty(const nsACString& aValue) {
1606   m_hdrProperty = aValue;
1607   ToLowerCaseExceptSpecials(m_hdrProperty);
1608   return NS_OK;
1609 }
1610 
NS_IMPL_GETSET(nsMsgSearchTerm,BeginsGrouping,bool,mBeginsGrouping)1611 NS_IMPL_GETSET(nsMsgSearchTerm, BeginsGrouping, bool, mBeginsGrouping)
1612 NS_IMPL_GETSET(nsMsgSearchTerm, EndsGrouping, bool, mEndsGrouping)
1613 
1614 //
1615 // Certain possible standard values of a message database row also sometimes
1616 // appear as header values. To prevent a naming collision, we use all
1617 // lower case for the standard headers, and first capital when those
1618 // same strings are requested as arbitrary headers. This routine is used
1619 // when setting arbitrary headers.
1620 //
1621 void nsMsgSearchTerm::ToLowerCaseExceptSpecials(nsACString& aValue) {
1622   if (aValue.LowerCaseEqualsLiteral("sender"))
1623     aValue.AssignLiteral("Sender");
1624   else if (aValue.LowerCaseEqualsLiteral("date"))
1625     aValue.AssignLiteral("Date");
1626   else if (aValue.LowerCaseEqualsLiteral("status"))
1627     aValue.AssignLiteral("Status");
1628   else
1629     ToLowerCase(aValue);
1630 }
1631 
1632 //-----------------------------------------------------------------------------
1633 // nsMsgSearchScopeTerm implementation
1634 //-----------------------------------------------------------------------------
nsMsgSearchScopeTerm(nsIMsgSearchSession * session,nsMsgSearchScopeValue attribute,nsIMsgFolder * folder)1635 nsMsgSearchScopeTerm::nsMsgSearchScopeTerm(nsIMsgSearchSession* session,
1636                                            nsMsgSearchScopeValue attribute,
1637                                            nsIMsgFolder* folder) {
1638   m_attribute = attribute;
1639   m_folder = folder;
1640   m_searchServer = true;
1641   m_searchSession = do_GetWeakReference(session);
1642 }
1643 
nsMsgSearchScopeTerm()1644 nsMsgSearchScopeTerm::nsMsgSearchScopeTerm() { m_searchServer = true; }
1645 
~nsMsgSearchScopeTerm()1646 nsMsgSearchScopeTerm::~nsMsgSearchScopeTerm() {
1647   if (m_inputStream) m_inputStream->Close();
1648   m_inputStream = nullptr;
1649 }
1650 
NS_IMPL_ISUPPORTS(nsMsgSearchScopeTerm,nsIMsgSearchScopeTerm)1651 NS_IMPL_ISUPPORTS(nsMsgSearchScopeTerm, nsIMsgSearchScopeTerm)
1652 
1653 NS_IMETHODIMP
1654 nsMsgSearchScopeTerm::GetFolder(nsIMsgFolder** aResult) {
1655   NS_IF_ADDREF(*aResult = m_folder);
1656   return NS_OK;
1657 }
1658 
1659 NS_IMETHODIMP
GetSearchSession(nsIMsgSearchSession ** aResult)1660 nsMsgSearchScopeTerm::GetSearchSession(nsIMsgSearchSession** aResult) {
1661   NS_ENSURE_ARG_POINTER(aResult);
1662   nsCOMPtr<nsIMsgSearchSession> searchSession =
1663       do_QueryReferent(m_searchSession);
1664   searchSession.forget(aResult);
1665   return NS_OK;
1666 }
1667 
1668 NS_IMETHODIMP
GetInputStream(nsIMsgDBHdr * aMsgHdr,nsIInputStream ** aInputStream)1669 nsMsgSearchScopeTerm::GetInputStream(nsIMsgDBHdr* aMsgHdr,
1670                                      nsIInputStream** aInputStream) {
1671   NS_ENSURE_ARG_POINTER(aInputStream);
1672   NS_ENSURE_ARG_POINTER(aMsgHdr);
1673   NS_ENSURE_TRUE(m_folder, NS_ERROR_NULL_POINTER);
1674   bool reusable;
1675   nsresult rv = m_folder->GetMsgInputStream(aMsgHdr, &reusable,
1676                                             getter_AddRefs(m_inputStream));
1677   NS_ENSURE_SUCCESS(rv, rv);
1678   NS_IF_ADDREF(*aInputStream = m_inputStream);
1679   return rv;
1680 }
1681 
CloseInputStream()1682 NS_IMETHODIMP nsMsgSearchScopeTerm::CloseInputStream() {
1683   if (m_inputStream) {
1684     m_inputStream->Close();
1685     m_inputStream = nullptr;
1686   }
1687   return NS_OK;
1688 }
1689 
TimeSlice(bool * aDone)1690 nsresult nsMsgSearchScopeTerm::TimeSlice(bool* aDone) {
1691   return m_adapter->Search(aDone);
1692 }
1693 
InitializeAdapter(nsTArray<RefPtr<nsIMsgSearchTerm>> const & termList)1694 nsresult nsMsgSearchScopeTerm::InitializeAdapter(
1695     nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList) {
1696   if (m_adapter) return NS_OK;
1697 
1698   nsresult rv = NS_OK;
1699 
1700   switch (m_attribute) {
1701     case nsMsgSearchScope::onlineMail:
1702       m_adapter = new nsMsgSearchOnlineMail(this, termList);
1703       break;
1704     case nsMsgSearchScope::offlineMail:
1705     case nsMsgSearchScope::onlineManual:
1706       m_adapter = new nsMsgSearchOfflineMail(this, termList);
1707       break;
1708     case nsMsgSearchScope::newsEx:
1709       NS_ASSERTION(false, "not supporting newsEx yet");
1710       break;
1711     case nsMsgSearchScope::news:
1712       m_adapter = new nsMsgSearchNews(this, termList);
1713       break;
1714     case nsMsgSearchScope::allSearchableGroups:
1715       NS_ASSERTION(false, "not supporting allSearchableGroups yet");
1716       break;
1717     case nsMsgSearchScope::LDAP:
1718       NS_ASSERTION(false, "not supporting LDAP yet");
1719       break;
1720     case nsMsgSearchScope::localNews:
1721     case nsMsgSearchScope::localNewsJunk:
1722     case nsMsgSearchScope::localNewsBody:
1723     case nsMsgSearchScope::localNewsJunkBody:
1724       m_adapter = new nsMsgSearchOfflineNews(this, termList);
1725       break;
1726     default:
1727       NS_ASSERTION(false, "invalid scope");
1728       rv = NS_ERROR_FAILURE;
1729   }
1730 
1731   if (m_adapter) rv = m_adapter->ValidateTerms();
1732 
1733   return rv;
1734 }
1735 
GetStatusBarName()1736 char* nsMsgSearchScopeTerm::GetStatusBarName() { return nullptr; }
1737 
1738 //-----------------------------------------------------------------------------
1739 // nsMsgResultElement implementation
1740 //-----------------------------------------------------------------------------
1741 
nsMsgResultElement(nsIMsgSearchAdapter * adapter)1742 nsMsgResultElement::nsMsgResultElement(nsIMsgSearchAdapter* adapter) {
1743   m_adapter = adapter;
1744 }
1745 
~nsMsgResultElement()1746 nsMsgResultElement::~nsMsgResultElement() {}
1747 
AddValue(nsIMsgSearchValue * value)1748 nsresult nsMsgResultElement::AddValue(nsIMsgSearchValue* value) {
1749   m_valueList.AppendElement(value);
1750   return NS_OK;
1751 }
1752 
AddValue(nsMsgSearchValue * value)1753 nsresult nsMsgResultElement::AddValue(nsMsgSearchValue* value) {
1754   nsMsgSearchValueImpl* valueImpl = new nsMsgSearchValueImpl(value);
1755   delete value;  // we keep the nsIMsgSearchValue, not
1756                  // the nsMsgSearchValue
1757   return AddValue(valueImpl);
1758 }
1759 
AssignValues(nsIMsgSearchValue * src,nsMsgSearchValue * dst)1760 nsresult nsMsgResultElement::AssignValues(nsIMsgSearchValue* src,
1761                                           nsMsgSearchValue* dst) {
1762   NS_ENSURE_ARG_POINTER(src);
1763   NS_ENSURE_ARG_POINTER(dst);
1764   // Yes, this could be an operator overload, but nsMsgSearchValue is totally
1765   // public, so I'd have to define a derived class with nothing by operator=,
1766   // and that seems like a bit much
1767   nsresult rv = NS_OK;
1768   src->GetAttrib(&dst->attribute);
1769   switch (dst->attribute) {
1770     case nsMsgSearchAttrib::Priority:
1771       rv = src->GetPriority(&dst->u.priority);
1772       break;
1773     case nsMsgSearchAttrib::Date:
1774       rv = src->GetDate(&dst->u.date);
1775       break;
1776     case nsMsgSearchAttrib::HasAttachmentStatus:
1777     case nsMsgSearchAttrib::MsgStatus:
1778     case nsMsgSearchAttrib::FolderFlag:
1779     case nsMsgSearchAttrib::Uint32HdrProperty:
1780       rv = src->GetStatus(&dst->u.msgStatus);
1781       break;
1782     case nsMsgSearchAttrib::MessageKey:
1783       rv = src->GetMsgKey(&dst->u.key);
1784       break;
1785     case nsMsgSearchAttrib::AgeInDays:
1786       rv = src->GetAge(&dst->u.age);
1787       break;
1788     case nsMsgSearchAttrib::Label:
1789       rv = src->GetLabel(&dst->u.label);
1790       break;
1791     case nsMsgSearchAttrib::JunkStatus:
1792       rv = src->GetJunkStatus(&dst->u.junkStatus);
1793       break;
1794     case nsMsgSearchAttrib::JunkPercent:
1795       rv = src->GetJunkPercent(&dst->u.junkPercent);
1796       break;
1797     case nsMsgSearchAttrib::Size:
1798       rv = src->GetSize(&dst->u.size);
1799       break;
1800     default:
1801       if (dst->attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes) {
1802         NS_ASSERTION(IS_STRING_ATTRIBUTE(dst->attribute),
1803                      "assigning non-string result");
1804         nsString unicodeString;
1805         rv = src->GetStr(unicodeString);
1806         CopyUTF16toUTF8(unicodeString, dst->utf8String);
1807         dst->utf16String = unicodeString;
1808       } else
1809         rv = NS_ERROR_INVALID_ARG;
1810   }
1811   return rv;
1812 }
1813 
GetValue(nsMsgSearchAttribValue attrib,nsMsgSearchValue ** outValue) const1814 nsresult nsMsgResultElement::GetValue(nsMsgSearchAttribValue attrib,
1815                                       nsMsgSearchValue** outValue) const {
1816   nsresult rv = NS_OK;
1817   *outValue = NULL;
1818 
1819   for (uint32_t i = 0; i < m_valueList.Length() && NS_FAILED(rv); i++) {
1820     nsMsgSearchAttribValue valueAttribute;
1821     m_valueList[i]->GetAttrib(&valueAttribute);
1822     if (attrib == valueAttribute) {
1823       *outValue = new nsMsgSearchValue;
1824       if (*outValue) {
1825         rv = AssignValues(m_valueList[i], *outValue);
1826         // Now this is really strange! What is this assignment doing here?
1827         rv = NS_OK;
1828       } else
1829         rv = NS_ERROR_OUT_OF_MEMORY;
1830     }
1831   }
1832   return rv;
1833 }
1834 
GetPrettyName(nsMsgSearchValue ** value)1835 nsresult nsMsgResultElement::GetPrettyName(nsMsgSearchValue** value) {
1836   return GetValue(nsMsgSearchAttrib::Location, value);
1837 }
1838 
Open(void * window)1839 nsresult nsMsgResultElement::Open(void* window) {
1840   return NS_ERROR_NULL_POINTER;
1841 }
1842