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