kmail

kmsearchpattern.cpp
1// kmsearchpattern.cpp
2// Author: Marc Mutz <Marc@Mutz.com>
3// This code is under GPL!
4
5#include <config.h>
6
7#include "kmaddrbook.h"
8#include "kmsearchpattern.h"
9#include "kmmsgdict.h"
10#include "filterlog.h"
11#include "kmkernel.h"
12#include "kmmsgdict.h"
13#include "kmfolder.h"
15
16#include <libemailfunctions/email.h>
17
18#include <tdeglobal.h>
19#include <tdelocale.h>
20#include <kdebug.h>
21#include <tdeconfig.h>
22
23#include <tdeabc/stdaddressbook.h>
24
25#include <tqregexp.h>
26
27#include <mimelib/string.h>
28#include <mimelib/boyermor.h>
29#include <mimelib/field.h>
30#include <mimelib/headers.h>
31
32#include <assert.h>
33
34static const char* funcConfigNames[] =
35 { "contains", "contains-not", "equals", "not-equal", "regexp",
36 "not-regexp", "greater", "less-or-equal", "less", "greater-or-equal",
37 "is-in-addressbook", "is-not-in-addressbook" , "is-in-category", "is-not-in-category",
38 "has-attachment", "has-no-attachment"};
39static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames;
40
41struct _statusNames {
42 const char* name;
43 KMMsgStatus status;
44};
45
46static struct _statusNames statusNames[] = {
47 { "Important", KMMsgStatusFlag },
48 { "New", KMMsgStatusNew },
49 { "Unread", KMMsgStatusUnread | KMMsgStatusNew },
50 { "Read", KMMsgStatusRead },
51 { "Old", KMMsgStatusOld },
52 { "Deleted", KMMsgStatusDeleted },
53 { "Replied", KMMsgStatusReplied },
54 { "Forwarded", KMMsgStatusForwarded },
55 { "Queued", KMMsgStatusQueued },
56 { "Sent", KMMsgStatusSent },
57 { "Watched", KMMsgStatusWatched },
58 { "Ignored", KMMsgStatusIgnored },
59 { "To Do", KMMsgStatusTodo },
60 { "Spam", KMMsgStatusSpam },
61 { "Ham", KMMsgStatusHam },
62 { "Has Attachment", KMMsgStatusHasAttach },
63 { "Invitation", KMMsgStatusHasInvitation }
64};
65
66static const int numStatusNames = sizeof statusNames / sizeof ( struct _statusNames );
67
68
69//==================================================
70//
71// class KMSearchRule (was: KMFilterRule)
72//
73//==================================================
74
75KMSearchRule::KMSearchRule( const TQCString & field, Function func, const TQString & contents )
76 : mField( field ),
77 mFunction( func ),
78 mContents( contents )
79{
80}
81
82KMSearchRule::KMSearchRule( const KMSearchRule & other )
83 : mField( other.mField ),
84 mFunction( other.mFunction ),
85 mContents( other.mContents )
86{
87}
88
89const KMSearchRule & KMSearchRule::operator=( const KMSearchRule & other ) {
90 if ( this == &other )
91 return *this;
92
93 mField = other.mField;
94 mFunction = other.mFunction;
95 mContents = other.mContents;
96
97 return *this;
98}
99
101 Function func,
102 const TQString & contents )
103{
104 KMSearchRule *ret = 0;
105 if (field == "<status>")
106 ret = new KMSearchRuleStatus( field, func, contents );
107 else if ( field == "<age in days>" || field == "<size>" )
108 ret = new KMSearchRuleNumerical( field, func, contents );
109 else
110 ret = new KMSearchRuleString( field, func, contents );
111
112 return ret;
113}
114
115KMSearchRule * KMSearchRule::createInstance( const TQCString & field,
116 const char *func,
117 const TQString & contents )
118{
119 return ( createInstance( field, configValueToFunc( func ), contents ) );
120}
121
123{
124 return ( createInstance( other.field(), other.function(), other.contents() ) );
125}
126
127KMSearchRule * KMSearchRule::createInstanceFromConfig( const TDEConfig * config, int aIdx )
128{
129 const char cIdx = char( int('A') + aIdx );
130
131 static const TQString & field = TDEGlobal::staticQString( "field" );
132 static const TQString & func = TDEGlobal::staticQString( "func" );
133 static const TQString & contents = TDEGlobal::staticQString( "contents" );
134
135 const TQCString &field2 = config->readEntry( field + cIdx ).latin1();
136 Function func2 = configValueToFunc( config->readEntry( func + cIdx ).latin1() );
137 const TQString & contents2 = config->readEntry( contents + cIdx );
138
139 if ( field2 == "<To or Cc>" ) // backwards compat
140 return KMSearchRule::createInstance( "<recipients>", func2, contents2 );
141 else
142 return KMSearchRule::createInstance( field2, func2, contents2 );
143}
144
145KMSearchRule::Function KMSearchRule::configValueToFunc( const char * str ) {
146 if ( !str )
147 return FuncNone;
148
149 for ( int i = 0 ; i < numFuncConfigNames ; ++i )
150 if ( tqstricmp( funcConfigNames[i], str ) == 0 ) return (Function)i;
151
152 return FuncNone;
153}
154
155TQString KMSearchRule::functionToString( Function function )
156{
157 if ( function != FuncNone )
158 return funcConfigNames[int( function )];
159 else
160 return "invalid";
161}
162
163void KMSearchRule::writeConfig( TDEConfig * config, int aIdx ) const {
164 const char cIdx = char('A' + aIdx);
165 static const TQString & field = TDEGlobal::staticQString( "field" );
166 static const TQString & func = TDEGlobal::staticQString( "func" );
167 static const TQString & contents = TDEGlobal::staticQString( "contents" );
168
169 config->writeEntry( field + cIdx, TQString(mField) );
170 config->writeEntry( func + cIdx, functionToString( mFunction ) );
171 config->writeEntry( contents + cIdx, mContents );
172}
173
174bool KMSearchRule::matches( const DwString & aStr, KMMessage & msg,
175 const DwBoyerMoore *, int ) const
176{
177 if ( !msg.isComplete() ) {
178 msg.fromDwString( aStr );
179 msg.setComplete( true );
180 }
181 return matches( &msg );
182}
183
184const TQString KMSearchRule::asString() const
185{
186 TQString result = "\"" + mField + "\" <";
187 result += functionToString( mFunction );
188 result += "> \"" + mContents + "\"";
189
190 return result;
191}
192
193//==================================================
194//
195// class KMSearchRuleString
196//
197//==================================================
198
199KMSearchRuleString::KMSearchRuleString( const TQCString & field,
200 Function func, const TQString & contents )
201 : KMSearchRule(field, func, contents)
202{
203 if ( field.isEmpty() || field[0] == '<' )
204 mBmHeaderField = 0;
205 else // make sure you handle the unrealistic case of the message starting with mField
206 mBmHeaderField = new DwBoyerMoore(("\n" + field + ": ").data());
207}
208
209KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString & other )
210 : KMSearchRule( other ),
211 mBmHeaderField( 0 )
212{
213 if ( other.mBmHeaderField )
214 mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
215}
216
217const KMSearchRuleString & KMSearchRuleString::operator=( const KMSearchRuleString & other )
218{
219 if ( this == &other )
220 return *this;
221
222 setField( other.field() );
223 mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
224 setFunction( other.function() );
225 setContents( other.contents() );
226 delete mBmHeaderField; mBmHeaderField = 0;
227 if ( other.mBmHeaderField )
228 mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
229
230 return *this;
231}
232
233KMSearchRuleString::~KMSearchRuleString()
234{
235 delete mBmHeaderField;
236 mBmHeaderField = 0;
237}
238
240{
241 return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
242}
243
245{
246 if (mBmHeaderField || (field() == "<recipients>" ))
247 return false;
248 return true;
249}
250
251bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg,
252 const DwBoyerMoore * aHeaderField, int aHeaderLen ) const
253{
254 if ( isEmpty() )
255 return false;
256
257 bool rc = false;
258
259 const DwBoyerMoore * headerField = aHeaderField ? aHeaderField : mBmHeaderField ;
260
261 const int headerLen = ( aHeaderLen > -1 ? aHeaderLen : field().length() ) + 2 ; // +1 for ': '
262
263 if ( headerField ) {
264 static const DwBoyerMoore lflf( "\n\n" );
265 static const DwBoyerMoore lfcrlf( "\n\r\n" );
266
267 size_t endOfHeader = lflf.FindIn( aStr, 0 );
268 if ( endOfHeader == DwString::npos )
269 endOfHeader = lfcrlf.FindIn( aStr, 0 );
270 const DwString headers = ( endOfHeader == DwString::npos ) ? aStr : aStr.substr( 0, endOfHeader );
271 // In case the searched header is at the beginning, we have to prepend
272 // a newline - see the comment in KMSearchRuleString constructor
273 DwString fakedHeaders( "\n" );
274 size_t start = headerField->FindIn( fakedHeaders.append( headers ), 0, false );
275 // if the header field doesn't exist then return false for positive
276 // functions and true for negated functions (e.g. "does not
277 // contain"); note that all negated string functions correspond
278 // to an odd value
279 if ( start == DwString::npos )
280 rc = ( ( function() & 1 ) == 1 );
281 else {
282 start += headerLen;
283 size_t stop = aStr.find( '\n', start );
284 char ch = '\0';
285 while ( stop != DwString::npos && ( ( ch = aStr.at( stop + 1 ) ) == ' ' || ch == '\t' ) )
286 stop = aStr.find( '\n', stop + 1 );
287 const int len = stop == DwString::npos ? aStr.length() - start : stop - start ;
288 const TQCString codedValue( aStr.data() + start, len + 1 );
289 const TQString msgContents = KMMsgBase::decodeRFC2047String( codedValue ).stripWhiteSpace(); // FIXME: This needs to be changed for IDN support.
290 rc = matchesInternal( msgContents );
291 }
292 } else if ( field() == "<recipients>" ) {
293 static const DwBoyerMoore to("\nTo: ");
294 static const DwBoyerMoore cc("\nCc: ");
295 static const DwBoyerMoore bcc("\nBcc: ");
296 // <recipients> "contains" "foo" is true if any of the fields contains
297 // "foo", while <recipients> "does not contain" "foo" is true if none
298 // of the fields contains "foo"
299 if ( ( function() & 1 ) == 0 ) {
300 // positive function, e.g. "contains"
301 rc = ( matches( aStr, msg, &to, 2 ) ||
302 matches( aStr, msg, &cc, 2 ) ||
303 matches( aStr, msg, &bcc, 3 ) );
304 }
305 else {
306 // negated function, e.g. "does not contain"
307 rc = ( matches( aStr, msg, &to, 2 ) &&
308 matches( aStr, msg, &cc, 2 ) &&
309 matches( aStr, msg, &bcc, 3 ) );
310 }
311 }
312 if ( FilterLog::instance()->isLogging() ) {
313 TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
314 : "<font color=#FF0000>0 = </font>" );
315 msg += FilterLog::recode( asString() );
316 // only log headers bcause messages and bodies can be pretty large
317// FIXME We have to separate the text which is used for filtering to be able to show it in the log
318// if ( logContents )
319// msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
320 FilterLog::instance()->add( msg, FilterLog::ruleResult );
321 }
322 return rc;
323}
324
325bool KMSearchRuleString::matches( const KMMessage * msg ) const
326{
327 assert( msg );
328
329 if ( isEmpty() )
330 return false;
331
332 TQString msgContents;
333 // Show the value used to compare the rules against in the log.
334 // Overwrite the value for complete messages and all headers!
335 bool logContents = true;
336
337 if( field() == "<message>" ) {
338
339 // When searching in the complete message, we can't simply use msg->asString() here,
340 // as that wouldn't decode the body. Therefore we use the decoded body and all decoded
341 // header fields and add all to the one big search string.
342 msgContents += msg->bodyToUnicode();
343 const DwHeaders& headers = msg->headers();
344 const DwField * dwField = headers.FirstField();
345 while( dwField != 0 ) {
346 const char * const fieldName = dwField->FieldNameStr().c_str();
347 const TQString fieldValue = msg->headerFields( fieldName ).join( " " );
348 msgContents += " " + fieldValue;
349 dwField = dwField->Next();
350 }
351 logContents = false;
352 } else if ( field() == "<body>" ) {
353 msgContents = msg->bodyToUnicode();
354 logContents = false;
355 } else if ( field() == "<any header>" ) {
356 msgContents = msg->headerAsString();
357 logContents = false;
358 } else if ( field() == "<recipients>" ) {
359 // (mmutz 2001-11-05) hack to fix "<recipients> !contains foo" to
360 // meet user's expectations. See FAQ entry in KDE 2.2.2's KMail
361 // handbook
362 if ( function() == FuncEquals || function() == FuncNotEqual )
363 // do we need to treat this case specially? Ie.: What shall
364 // "equality" mean for recipients.
365 return matchesInternal( msg->headerField("To") )
366 || matchesInternal( msg->headerField("Cc") )
367 || matchesInternal( msg->headerField("Bcc") )
368 // sometimes messages have multiple Cc headers
369 || matchesInternal( msg->cc() );
370
371 msgContents = msg->headerField("To");
372 if ( !msg->headerField("Cc").compare( msg->cc() ) )
373 msgContents += ", " + msg->headerField("Cc");
374 else
375 msgContents += ", " + msg->cc();
376 msgContents += ", " + msg->headerField("Bcc");
377 } else {
378 // make sure to treat messages with multiple header lines for
379 // the same header correctly
380 msgContents = msg->headerFields( field() ).join( " " );
381 }
382
383 if ( function() == FuncIsInAddressbook ||
384 function() == FuncIsNotInAddressbook ) {
385 // I think only the "from"-field makes sense.
386 msgContents = msg->headerField( field() );
387 if ( msgContents.isEmpty() )
388 return ( function() == FuncIsInAddressbook ) ? false : true;
389 }
390
391 // these two functions need the kmmessage therefore they don't call matchesInternal
392 if ( function() == FuncHasAttachment )
393 return ( msg->toMsgBase().attachmentState() == KMMsgHasAttachment );
394 if ( function() == FuncHasNoAttachment )
395 return ( ((KMMsgAttachmentState) msg->toMsgBase().attachmentState()) == KMMsgHasNoAttachment );
396
397 bool rc = matchesInternal( msgContents );
398 if ( FilterLog::instance()->isLogging() ) {
399 TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
400 : "<font color=#FF0000>0 = </font>" );
401 msg += FilterLog::recode( asString() );
402 // only log headers bcause messages and bodies can be pretty large
403 if ( logContents )
404 msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
405 FilterLog::instance()->add( msg, FilterLog::ruleResult );
406 }
407 return rc;
408}
409
410// helper, does the actual comparing
411bool KMSearchRuleString::matchesInternal( const TQString & msgContents ) const
412{
413 switch ( function() ) {
414 case KMSearchRule::FuncEquals:
415 return ( TQString::compare( msgContents.lower(), contents().lower() ) == 0 );
416
417 case KMSearchRule::FuncNotEqual:
418 return ( TQString::compare( msgContents.lower(), contents().lower() ) != 0 );
419
420 case KMSearchRule::FuncContains:
421 return ( msgContents.find( contents(), 0, false ) >= 0 );
422
423 case KMSearchRule::FuncContainsNot:
424 return ( msgContents.find( contents(), 0, false ) < 0 );
425
426 case KMSearchRule::FuncRegExp:
427 {
428 TQRegExp regexp( contents(), false );
429 return ( regexp.search( msgContents ) >= 0 );
430 }
431
432 case KMSearchRule::FuncNotRegExp:
433 {
434 TQRegExp regexp( contents(), false );
435 return ( regexp.search( msgContents ) < 0 );
436 }
437
438 case FuncIsGreater:
439 return ( TQString::compare( msgContents.lower(), contents().lower() ) > 0 );
440
441 case FuncIsLessOrEqual:
442 return ( TQString::compare( msgContents.lower(), contents().lower() ) <= 0 );
443
444 case FuncIsLess:
445 return ( TQString::compare( msgContents.lower(), contents().lower() ) < 0 );
446
447 case FuncIsGreaterOrEqual:
448 return ( TQString::compare( msgContents.lower(), contents().lower() ) >= 0 );
449
450 case FuncIsInAddressbook: {
451 TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true );
452 TQStringList addressList =
453 KPIM::splitEmailAddrList( msgContents.lower() );
454 for( TQStringList::ConstIterator it = addressList.begin();
455 ( it != addressList.end() );
456 ++it ) {
457 if ( !stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
458 return true;
459 }
460 return false;
461 }
462
463 case FuncIsNotInAddressbook: {
464 TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true );
465 TQStringList addressList =
466 KPIM::splitEmailAddrList( msgContents.lower() );
467 for( TQStringList::ConstIterator it = addressList.begin();
468 ( it != addressList.end() );
469 ++it ) {
470 if ( stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
471 return true;
472 }
473 return false;
474 }
475
476 case FuncIsInCategory: {
477 TQString category = contents();
478 TQStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() );
479 TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true );
480
481 for( TQStringList::ConstIterator it = addressList.begin();
482 it != addressList.end(); ++it ) {
483 TDEABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
484
485 for ( TDEABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
486 if ( (*itAd).hasCategory(category) )
487 return true;
488
489 }
490 return false;
491 }
492
493 case FuncIsNotInCategory: {
494 TQString category = contents();
495 TQStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() );
496 TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true );
497
498 for( TQStringList::ConstIterator it = addressList.begin();
499 it != addressList.end(); ++it ) {
500 TDEABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
501
502 for ( TDEABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
503 if ( (*itAd).hasCategory(category) )
504 return false;
505
506 }
507 return true;
508 }
509 default:
510 ;
511 }
512
513 return false;
514}
515
516
517//==================================================
518//
519// class KMSearchRuleNumerical
520//
521//==================================================
522
523KMSearchRuleNumerical::KMSearchRuleNumerical( const TQCString & field,
524 Function func, const TQString & contents )
525 : KMSearchRule(field, func, contents)
526{
527}
528
530{
531 bool ok = false;
532 contents().toInt( &ok );
533
534 return !ok;
535}
536
537
539{
540
541 TQString msgContents;
542 int numericalMsgContents = 0;
543 int numericalValue = 0;
544
545 if ( field() == "<size>" ) {
546 numericalMsgContents = int( msg->msgLength() );
547 numericalValue = contents().toInt();
548 msgContents.setNum( numericalMsgContents );
549 } else if ( field() == "<age in days>" ) {
550 TQDateTime msgDateTime;
551 msgDateTime.setTime_t( msg->date() );
552 numericalMsgContents = msgDateTime.daysTo( TQDateTime::currentDateTime() );
553 numericalValue = contents().toInt();
554 msgContents.setNum( numericalMsgContents );
555 }
556 bool rc = matchesInternal( numericalValue, numericalMsgContents, msgContents );
557 if ( FilterLog::instance()->isLogging() ) {
558 TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
559 : "<font color=#FF0000>0 = </font>" );
560 msg += FilterLog::recode( asString() );
561 msg += " ( <i>" + TQString::number( numericalMsgContents ) + "</i> )";
562 FilterLog::instance()->add( msg, FilterLog::ruleResult );
563 }
564 return rc;
565}
566
568 long numericalMsgContents, const TQString & msgContents ) const
569{
570 switch ( function() ) {
571 case KMSearchRule::FuncEquals:
572 return ( numericalValue == numericalMsgContents );
573
574 case KMSearchRule::FuncNotEqual:
575 return ( numericalValue != numericalMsgContents );
576
577 case KMSearchRule::FuncContains:
578 return ( msgContents.find( contents(), 0, false ) >= 0 );
579
580 case KMSearchRule::FuncContainsNot:
581 return ( msgContents.find( contents(), 0, false ) < 0 );
582
583 case KMSearchRule::FuncRegExp:
584 {
585 TQRegExp regexp( contents(), false );
586 return ( regexp.search( msgContents ) >= 0 );
587 }
588
589 case KMSearchRule::FuncNotRegExp:
590 {
591 TQRegExp regexp( contents(), false );
592 return ( regexp.search( msgContents ) < 0 );
593 }
594
595 case FuncIsGreater:
596 return ( numericalMsgContents > numericalValue );
597
598 case FuncIsLessOrEqual:
599 return ( numericalMsgContents <= numericalValue );
600
601 case FuncIsLess:
602 return ( numericalMsgContents < numericalValue );
603
604 case FuncIsGreaterOrEqual:
605 return ( numericalMsgContents >= numericalValue );
606
607 case FuncIsInAddressbook: // since email-addresses are not numerical, I settle for false here
608 return false;
609
610 case FuncIsNotInAddressbook:
611 return false;
612
613 default:
614 ;
615 }
616
617 return false;
618}
619
620
621
622//==================================================
623//
624// class KMSearchRuleStatus
625//
626//==================================================
627TQString englishNameForStatus( const KMMsgStatus& status )
628{
629 for ( int i=0; i< numStatusNames; i++ ) {
630 if ( statusNames[i].status == status ) {
631 return statusNames[i].name;
632 }
633 }
634 return TQString();
635}
636
637KMSearchRuleStatus::KMSearchRuleStatus( const TQCString & field,
638 Function func, const TQString & aContents )
639 : KMSearchRule(field, func, aContents)
640{
641 // the values are always in english, both from the conf file as well as
642 // the patternedit gui
643 mStatus = statusFromEnglishName( aContents );
644}
645
646KMSearchRuleStatus::KMSearchRuleStatus( int status, Function func )
647: KMSearchRule( "<status>", func, englishNameForStatus( status ) )
648{
649 mStatus = status;
650}
651
652KMMsgStatus KMSearchRuleStatus::statusFromEnglishName( const TQString & aStatusString )
653{
654 for ( int i=0; i< numStatusNames; i++ ) {
655 if ( !aStatusString.compare( statusNames[i].name ) ) {
656 return statusNames[i].status;
657 }
658 }
659 return KMMsgStatusUnknown;
660}
661
663{
664 return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
665}
666
667bool KMSearchRuleStatus::matches( const DwString &, KMMessage &,
668 const DwBoyerMoore *, int ) const
669{
670 assert( 0 );
671 return false; // don't warn
672}
673
674bool KMSearchRuleStatus::matches( const KMMessage * msg ) const
675{
676
677 KMMsgStatus msgStatus = msg->status();
678 bool rc = false;
679
680 switch ( function() ) {
681 case FuncEquals: // fallthrough. So that "<status> 'is' 'read'" works
682 case FuncContains:
683 if (msgStatus & mStatus)
684 rc = true;
685 break;
686 case FuncNotEqual: // fallthrough. So that "<status> 'is not' 'read'" works
687 case FuncContainsNot:
688 if (! (msgStatus & mStatus) )
689 rc = true;
690 break;
691 // FIXME what about the remaining funcs, how can they make sense for
692 // stati?
693 default:
694 break;
695 }
696
697 if ( FilterLog::instance()->isLogging() ) {
698 TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
699 : "<font color=#FF0000>0 = </font>" );
700 msg += FilterLog::recode( asString() );
701 FilterLog::instance()->add( msg, FilterLog::ruleResult );
702 }
703 return rc;
704}
705
706// ----------------------------------------------------------------------------
707
708//==================================================
709//
710// class KMSearchPattern
711//
712//==================================================
713
714KMSearchPattern::KMSearchPattern( const TDEConfig * config )
715 : TQPtrList<KMSearchRule>()
716{
717 setAutoDelete( true );
718 if ( config )
719 readConfig( config );
720 else
721 init();
722}
723
725{
726}
727
728bool KMSearchPattern::matches( const KMMessage * msg, bool ignoreBody ) const
729{
730 if ( isEmpty() )
731 return true;
732
733 TQPtrListIterator<KMSearchRule> it( *this );
734 switch ( mOperator ) {
735 case OpAnd: // all rules must match
736 for ( it.toFirst() ; it.current() ; ++it )
737 if ( !((*it)->requiresBody() && ignoreBody) )
738 if ( !(*it)->matches( msg ) )
739 return false;
740 return true;
741 case OpOr: // at least one rule must match
742 for ( it.toFirst() ; it.current() ; ++it )
743 if ( !((*it)->requiresBody() && ignoreBody) )
744 if ( (*it)->matches( msg ) )
745 return true;
746 // fall through
747 default:
748 return false;
749 }
750}
751
752bool KMSearchPattern::matches( const DwString & aStr, bool ignoreBody ) const
753{
754 if ( isEmpty() )
755 return true;
756
757 KMMessage msg;
758 TQPtrListIterator<KMSearchRule> it( *this );
759 switch ( mOperator ) {
760 case OpAnd: // all rules must match
761 for ( it.toFirst() ; it.current() ; ++it )
762 if ( !((*it)->requiresBody() && ignoreBody) )
763 if ( !(*it)->matches( aStr, msg ) )
764 return false;
765 return true;
766 case OpOr: // at least one rule must match
767 for ( it.toFirst() ; it.current() ; ++it )
768 if ( !((*it)->requiresBody() && ignoreBody) )
769 if ( (*it)->matches( aStr, msg ) )
770 return true;
771 // fall through
772 default:
773 return false;
774 }
775}
776
777bool KMSearchPattern::matches( TQ_UINT32 serNum, bool ignoreBody ) const
778{
779 if ( isEmpty() )
780 return true;
781
782 bool res;
783 int idx = -1;
784 KMFolder *folder = 0;
785 KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
786 if (!folder || (idx == -1) || (idx >= folder->count())) {
787 return false;
788 }
789
790 KMFolderOpener openFolder(folder, "searchptr");
791 KMMsgBase *msgBase = folder->getMsgBase(idx);
792 if (requiresBody() && !ignoreBody) {
793 bool unGet = !msgBase->isMessage();
794 KMMessage *msg = folder->getMsg(idx);
795 res = false;
796 if ( msg ) {
797 res = matches( msg, ignoreBody );
798 if (unGet)
799 folder->unGetMsg(idx);
800 }
801 } else {
802 res = matches( folder->getDwString(idx), ignoreBody );
803 }
804 return res;
805}
806
808 TQPtrListIterator<KMSearchRule> it( *this );
809 for ( it.toFirst() ; it.current() ; ++it )
810 if ( (*it)->requiresBody() )
811 return true;
812 return false;
813}
814
816 TQPtrListIterator<KMSearchRule> it( *this );
817 it.toLast();
818 while ( it.current() )
819 if ( (*it)->isEmpty() ) {
820#ifndef NDEBUG
821 kdDebug(5006) << "KMSearchPattern::purify(): removing " << (*it)->asString() << endl;
822#endif
823 remove( *it );
824 } else {
825 --it;
826 }
827}
828
829void KMSearchPattern::readConfig( const TDEConfig * config ) {
830 init();
831
832 mName = config->readEntry("name");
833 if ( !config->hasKey("rules") ) {
834 kdDebug(5006) << "KMSearchPattern::readConfig: found legacy config! Converting." << endl;
835 importLegacyConfig( config );
836 return;
837 }
838
839 mOperator = config->readEntry("operator") == "or" ? OpOr : OpAnd;
840
841 const int nRules = config->readNumEntry( "rules", 0 );
842
843 for ( int i = 0 ; i < nRules ; i++ ) {
845 if ( r->isEmpty() )
846 delete r;
847 else
848 append( r );
849 }
850}
851
852void KMSearchPattern::importLegacyConfig( const TDEConfig * config ) {
853 KMSearchRule * rule = KMSearchRule::createInstance( config->readEntry("fieldA").latin1(),
854 config->readEntry("funcA").latin1(),
855 config->readEntry("contentsA") );
856 if ( rule->isEmpty() ) {
857 // if the first rule is invalid,
858 // we really can't do much heuristics...
859 delete rule;
860 return;
861 }
862 append( rule );
863
864 const TQString sOperator = config->readEntry("operator");
865 if ( sOperator == "ignore" ) return;
866
867 rule = KMSearchRule::createInstance( config->readEntry("fieldB").latin1(),
868 config->readEntry("funcB").latin1(),
869 config->readEntry("contentsB") );
870 if ( rule->isEmpty() ) {
871 delete rule;
872 return;
873 }
874 append( rule );
875
876 if ( sOperator == "or" ) {
877 mOperator = OpOr;
878 return;
879 }
880 // This is the interesting case...
881 if ( sOperator == "unless" ) { // meaning "and not", ie we need to...
882 // ...invert the function (e.g. "equals" <-> "doesn't equal")
883 // We simply toggle the last bit (xor with 0x1)... This assumes that
884 // KMSearchRule::Function's come in adjacent pairs of pros and cons
885 KMSearchRule::Function func = last()->function();
886 unsigned int intFunc = (unsigned int)func;
887 func = KMSearchRule::Function( intFunc ^ 0x1 );
888
889 last()->setFunction( func );
890 }
891
892 // treat any other case as "and" (our default).
893}
894
895void KMSearchPattern::writeConfig( TDEConfig * config ) const {
896 config->writeEntry("name", mName);
897 config->writeEntry("operator", (mOperator == KMSearchPattern::OpOr) ? "or" : "and" );
898
899 int i = 0;
900 for ( TQPtrListIterator<KMSearchRule> it( *this ) ; it.current() && i < FILTER_MAX_RULES ; ++i , ++it )
901 // we could do this ourselves, but we want the rules to be extensible,
902 // so we give the rule it's number and let it do the rest.
903 (*it)->writeConfig( config, i );
904
905 // save the total number of rules.
906 config->writeEntry( "rules", i );
907}
908
909void KMSearchPattern::init() {
910 clear();
911 mOperator = OpAnd;
912 mName = '<' + i18n("name used for a virgin filter","unknown") + '>';
913}
914
916 TQString result;
917 if ( mOperator == OpOr )
918 result = i18n("(match any of the following)");
919 else
920 result = i18n("(match all of the following)");
921
922 for ( TQPtrListIterator<KMSearchRule> it( *this ) ; it.current() ; ++it )
923 result += "\n\t" + FilterLog::recode( (*it)->asString() );
924
925 return result;
926}
927
929 if ( this == &other )
930 return *this;
931
932 setOp( other.op() );
933 setName( other.name() );
934
935 clear(); // ###
936
937 for ( TQPtrListIterator<KMSearchRule> it( other ) ; it.current() ; ++it ) {
938 KMSearchRule * rule = KMSearchRule::createInstance( **it ); // deep copy
939 append( rule );
940 }
941
942 return *this;
943}
RAII for KMFolder::open() / close().
Definition: kmfolder.h:688
Mail folder.
Definition: kmfolder.h:69
KMMsgInfo * unGetMsg(int idx)
Replace KMMessage with KMMsgInfo and delete KMMessage
Definition: kmfolder.cpp:326
DwString getDwString(int idx)
Read a message and returns a DwString.
Definition: kmfolder.cpp:336
KMMessage * getMsg(int idx)
Read message at given index.
Definition: kmfolder.cpp:321
const KMMsgBase * getMsgBase(int idx) const
Provides access to the basic message fields that are also stored in the index.
Definition: kmfolder.cpp:360
int count(bool cache=false) const
Number of messages in this folder.
Definition: kmfolder.cpp:445
This is a Mime Message.
Definition: kmmessage.h:68
void fromDwString(const DwString &str, bool setStatus=false)
Parse the string and create this message from it.
Definition: kmmessage.cpp:402
size_t msgLength() const
Unlike the above function this works also, if the message is not in a folder.
Definition: kmmessage.h:817
TQStringList headerFields(const TQCString &name) const
Returns a list of the values of all header fields with the given name.
Definition: kmmessage.cpp:2302
TQString bodyToUnicode(const TQTextCodec *codec=0) const
Returns the body part decoded to unicode.
Definition: kmmessage.cpp:4471
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition: kmmessage.h:114
TQString cc() const
Get or set the 'Cc' header field.
Definition: kmmessage.cpp:1940
void setComplete(bool v)
Set if the message is a complete message.
Definition: kmmessage.h:869
KMMsgStatus status() const
Status of the message.
Definition: kmmessage.h:830
TQString headerField(const TQCString &name) const
Returns the value of a header field with the given name.
Definition: kmmessage.cpp:2289
DwHeaders & headers() const
get the DwHeaders (make sure to call setNeedsAssembly() function after directly modyfying internal da...
Definition: kmmessage.cpp:2546
bool isComplete() const
Return true if the complete message is available without referring to the backing store.
Definition: kmmessage.h:867
TQString headerAsString() const
Return header as string.
Definition: kmmessage.cpp:378
void getLocation(unsigned long key, KMFolder **retFolder, int *retIndex) const
Returns the folder the message represented by the serial number key is in and the index in that folde...
Definition: kmmsgdict.cpp:319
static const KMMsgDict * instance()
Access the globally unique MessageDict.
Definition: kmmsgdict.cpp:167
This class is an abstraction of a search over messages.
bool requiresBody() const
Returns true if the pattern only depends the DwString that backs a message.
KMSearchPattern::Operator op() const
Get the filter operator.
~KMSearchPattern()
Destructor.
void readConfig(const TDEConfig *config)
Reads a search pattern from a TDEConfig.
bool matches(const KMMessage *msg, bool ignoreBody=false) const
The central function of this class.
KMSearchPattern(const TDEConfig *config=0)
Constructor that initializes from a given TDEConfig group, if given.
void setOp(KMSearchPattern::Operator aOp)
Set the filter operator.
void setName(const TQString &newName)
Set the name of the search pattern.
TQString name() const
Get the name of the search pattern.
void purify()
Removes all empty rules from the list.
const KMSearchPattern & operator=(const KMSearchPattern &aPattern)
Overloaded assignment operator.
TQString asString() const
Returns the pattern as string.
void writeConfig(TDEConfig *config) const
Writes itself into config.
This class represents a search to be performed against a numerical value, such as the age of the mess...
virtual bool isEmpty() const
Determine whether the rule is worth considering.
bool matchesInternal(long numericalValue, long numericalMsgContents, const TQString &msgContents) const
Helper for the main matches() method.
virtual bool matches(const KMMessage *msg) const
Tries to match the rule against the given KMMessage.
This class represents a search to be performed against the status of a messsage.
virtual bool matches(const KMMessage *msg) const
Tries to match the rule against the given KMMessage.
virtual bool isEmpty() const
Determine whether the rule is worth considering.
This class represents a search to be performed against a string.
virtual bool requiresBody() const
Returns true if the rule depends on a complete message, otherwise returns false.
virtual bool matches(const KMMessage *msg) const
Tries to match the rule against the given KMMessage.
bool matchesInternal(const TQString &msgContents) const
Helper for the main matches() method.
virtual bool isEmpty() const
Determine whether the rule is worth considering.
Incoming mail is sent through the list of mail filter rules before it is placed in the associated mai...
void setFunction(Function aFunction)
Set filter function.
virtual bool matches(const KMMessage *msg) const =0
Tries to match the rule against the given KMMessage.
void setContents(const TQString &aContents)
Set the value.
static KMSearchRule * createInstanceFromConfig(const TDEConfig *config, int aIdx)
Initialize the object from a given config file.
static KMSearchRule * createInstance(const TQCString &field=0, Function function=FuncContains, const TQString &contents=TQString())
Create a search rule of a certain type by instantiating the appro- priate subclass depending on the f...
TQCString field() const
Return message header field name (without the trailing ':').
Function function() const
Return filter function.
const TQString asString() const
Returns the rule as string.
TQString contents() const
Return the value.
void setField(const TQCString &field)
Set message header field name (make sure there's no trailing colon ':')
virtual bool isEmpty() const =0
Determine whether the rule is worth considering.
Function
Operators for comparison of field and contents.
void writeConfig(TDEConfig *config, int aIdx) const
Save the object into a given config file.
KMail Filter Log Collector.
Definition: filterlog.h:54
void append(TQByteArray &that, const TQByteArray &str)
Append a bytearray to a bytearray.
Definition: util.cpp:144