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 }