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