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 }