kmail

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