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"
14 using KMail::FilterLog;
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 
34 static 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"};
39 static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames;
40 
41 struct _statusNames {
42  const char* name;
43  KMMsgStatus status;
44 };
45 
46 static 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 
66 static const int numStatusNames = sizeof statusNames / sizeof ( struct _statusNames );
67 
68 
69 //==================================================
70 //
71 // class KMSearchRule (was: KMFilterRule)
72 //
73 //==================================================
74 
75 KMSearchRule::KMSearchRule( const TQCString & field, Function func, const TQString & contents )
76  : mField( field ),
77  mFunction( func ),
78  mContents( contents )
79 {
80 }
81 
82 KMSearchRule::KMSearchRule( const KMSearchRule & other )
83  : mField( other.mField ),
84  mFunction( other.mFunction ),
85  mContents( other.mContents )
86 {
87 }
88 
89 const 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 
100 KMSearchRule * KMSearchRule::createInstance( const TQCString & field,
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 
115 KMSearchRule * 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 
127 KMSearchRule * 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 
145 KMSearchRule::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 
155 TQString KMSearchRule::functionToString( Function function )
156 {
157  if ( function != FuncNone )
158  return funcConfigNames[int( function )];
159  else
160  return "invalid";
161 }
162 
163 void 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 
174 bool 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 
184 const 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 
199 KMSearchRuleString::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 
209 KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString & other )
210  : KMSearchRule( other ),
211  mBmHeaderField( 0 )
212 {
213  if ( other.mBmHeaderField )
214  mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
215 }
216 
217 const 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 
233 KMSearchRuleString::~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 
251 bool 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 
325 bool 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
411 bool 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 
523 KMSearchRuleNumerical::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 
538 bool KMSearchRuleNumerical::matches( const KMMessage * msg ) const
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 
567 bool KMSearchRuleNumerical::matchesInternal( long numericalValue,
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 //==================================================
627 TQString 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 
637 KMSearchRuleStatus::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 
646 KMSearchRuleStatus::KMSearchRuleStatus( int status, Function func )
647 : KMSearchRule( "<status>", func, englishNameForStatus( status ) )
648 {
649  mStatus = status;
650 }
651 
652 KMMsgStatus 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 
667 bool KMSearchRuleStatus::matches( const DwString &, KMMessage &,
668  const DwBoyerMoore *, int ) const
669 {
670  assert( 0 );
671  return false; // don't warn
672 }
673 
674 bool 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 
714 KMSearchPattern::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 
728 bool 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 
752 bool 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 
777 bool 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 
829 void 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 
852 void 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 
895 void 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 
909 void KMSearchPattern::init() {
910  clear();
911  mOperator = OpAnd;
912  mName = '<' + i18n("name used for a virgin filter","unknown") + '>';
913 }
914 
915 TQString KMSearchPattern::asString() const {
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
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
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition: kmmessage.h:114
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