kmail

kmsearchpattern.cpp
00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 // kmsearchpattern.cpp
00003 // Author: Marc Mutz <Marc@Mutz.com>
00004 // This code is under GPL!
00005 
00006 #include <config.h>
00007 
00008 #include "kmaddrbook.h"
00009 #include "kmsearchpattern.h"
00010 #include "kmmsgdict.h"
00011 #include "filterlog.h"
00012 #include "kmkernel.h"
00013 #include "kmmsgdict.h"
00014 #include "kmfolder.h"
00015 using KMail::FilterLog;
00016 
00017 #include <libemailfunctions/email.h>
00018 
00019 #include <kglobal.h>
00020 #include <klocale.h>
00021 #include <kdebug.h>
00022 #include <kconfig.h>
00023 
00024 #include <kabc/stdaddressbook.h>
00025 
00026 #include <tqregexp.h>
00027 
00028 #include <mimelib/string.h>
00029 #include <mimelib/boyermor.h>
00030 #include <mimelib/field.h>
00031 #include <mimelib/headers.h>
00032 
00033 #include <assert.h>
00034 
00035 static const char* funcConfigNames[] =
00036   { "contains", "contains-not", "equals", "not-equal", "regexp",
00037     "not-regexp", "greater", "less-or-equal", "less", "greater-or-equal",
00038     "is-in-addressbook", "is-not-in-addressbook" , "is-in-category", "is-not-in-category",
00039     "has-attachment", "has-no-attachment"};
00040 static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames;
00041 
00042 struct _statusNames {
00043   const char* name;
00044   KMMsgStatus status;
00045 };
00046 
00047 static struct _statusNames statusNames[] = {
00048   { "Important", KMMsgStatusFlag },
00049   { "New", KMMsgStatusNew },
00050   { "Unread", KMMsgStatusUnread | KMMsgStatusNew },
00051   { "Read", KMMsgStatusRead },
00052   { "Old", KMMsgStatusOld },
00053   { "Deleted", KMMsgStatusDeleted },
00054   { "Replied", KMMsgStatusReplied },
00055   { "Forwarded", KMMsgStatusForwarded },
00056   { "Queued", KMMsgStatusQueued },
00057   { "Sent", KMMsgStatusSent },
00058   { "Watched", KMMsgStatusWatched },
00059   { "Ignored", KMMsgStatusIgnored },
00060   { "To Do", KMMsgStatusTodo },
00061   { "Spam", KMMsgStatusSpam },
00062   { "Ham", KMMsgStatusHam },
00063   { "Has Attachment", KMMsgStatusHasAttach },
00064   { "Invitation", KMMsgStatusHasInvitation }
00065 };
00066 
00067 static const int numStatusNames = sizeof statusNames / sizeof ( struct _statusNames );
00068 
00069 
00070 //==================================================
00071 //
00072 // class KMSearchRule (was: KMFilterRule)
00073 //
00074 //==================================================
00075 
00076 KMSearchRule::KMSearchRule( const TQCString & field, Function func, const TQString & contents )
00077   : mField( field ),
00078     mFunction( func ),
00079     mContents( contents )
00080 {
00081 }
00082 
00083 KMSearchRule::KMSearchRule( const KMSearchRule & other )
00084   : mField( other.mField ),
00085     mFunction( other.mFunction ),
00086     mContents( other.mContents )
00087 {
00088 }
00089 
00090 const KMSearchRule & KMSearchRule::operator=( const KMSearchRule & other ) {
00091   if ( this == &other )
00092     return *this;
00093 
00094   mField = other.mField;
00095   mFunction = other.mFunction;
00096   mContents = other.mContents;
00097 
00098   return *this;
00099 }
00100 
00101 KMSearchRule * KMSearchRule::createInstance( const TQCString & field,
00102                                              Function func,
00103                                              const TQString & contents )
00104 {
00105   KMSearchRule *ret = 0;
00106   if (field == "<status>")
00107     ret = new KMSearchRuleStatus( field, func, contents );
00108   else if ( field == "<age in days>" || field == "<size>" )
00109     ret = new KMSearchRuleNumerical( field, func, contents );
00110   else
00111     ret = new KMSearchRuleString( field, func, contents );
00112 
00113   return ret;
00114 }
00115 
00116 KMSearchRule * KMSearchRule::createInstance( const TQCString & field,
00117                                      const char *func,
00118                                      const TQString & contents )
00119 {
00120   return ( createInstance( field, configValueToFunc( func ), contents ) );
00121 }
00122 
00123 KMSearchRule * KMSearchRule::createInstance( const KMSearchRule & other )
00124 {
00125   return ( createInstance( other.field(), other.function(), other.contents() ) );
00126 }
00127 
00128 KMSearchRule * KMSearchRule::createInstanceFromConfig( const KConfig * config, int aIdx )
00129 {
00130   const char cIdx = char( int('A') + aIdx );
00131 
00132   static const TQString & field = KGlobal::staticQString( "field" );
00133   static const TQString & func = KGlobal::staticQString( "func" );
00134   static const TQString & contents = KGlobal::staticQString( "contents" );
00135 
00136   const TQCString &field2 = config->readEntry( field + cIdx ).latin1();
00137   Function func2 = configValueToFunc( config->readEntry( func + cIdx ).latin1() );
00138   const TQString & contents2 = config->readEntry( contents + cIdx );
00139 
00140   if ( field2 == "<To or Cc>" ) // backwards compat
00141     return KMSearchRule::createInstance( "<recipients>", func2, contents2 );
00142   else
00143     return KMSearchRule::createInstance( field2, func2, contents2 );
00144 }
00145 
00146 KMSearchRule::Function KMSearchRule::configValueToFunc( const char * str ) {
00147   if ( !str )
00148     return FuncNone;
00149 
00150   for ( int i = 0 ; i < numFuncConfigNames ; ++i )
00151     if ( qstricmp( funcConfigNames[i], str ) == 0 ) return (Function)i;
00152 
00153   return FuncNone;
00154 }
00155 
00156 TQString KMSearchRule::functionToString( Function function )
00157 {
00158   if ( function != FuncNone )
00159     return funcConfigNames[int( function )];
00160   else
00161     return "invalid";
00162 }
00163 
00164 void KMSearchRule::writeConfig( KConfig * config, int aIdx ) const {
00165   const char cIdx = char('A' + aIdx);
00166   static const TQString & field = KGlobal::staticQString( "field" );
00167   static const TQString & func = KGlobal::staticQString( "func" );
00168   static const TQString & contents = KGlobal::staticQString( "contents" );
00169 
00170   config->writeEntry( field + cIdx, TQString(mField) );
00171   config->writeEntry( func + cIdx, functionToString( mFunction ) );
00172   config->writeEntry( contents + cIdx, mContents );
00173 }
00174 
00175 bool KMSearchRule::matches( const DwString & aStr, KMMessage & msg,
00176                        const DwBoyerMoore *, int ) const
00177 {
00178   if ( !msg.isComplete() ) {
00179     msg.fromDwString( aStr );
00180     msg.setComplete( true );
00181   }
00182   return matches( &msg );
00183 }
00184 
00185 const TQString KMSearchRule::asString() const
00186 {
00187   TQString result  = "\"" + mField + "\" <";
00188   result += functionToString( mFunction );
00189   result += "> \"" + mContents + "\"";
00190 
00191   return result;
00192 }
00193 
00194 //==================================================
00195 //
00196 // class KMSearchRuleString
00197 //
00198 //==================================================
00199 
00200 KMSearchRuleString::KMSearchRuleString( const TQCString & field,
00201                                         Function func, const TQString & contents )
00202           : KMSearchRule(field, func, contents)
00203 {
00204   if ( field.isEmpty() || field[0] == '<' )
00205     mBmHeaderField = 0;
00206   else // make sure you handle the unrealistic case of the message starting with mField
00207     mBmHeaderField = new DwBoyerMoore(("\n" + field + ": ").data());
00208 }
00209 
00210 KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString & other )
00211   : KMSearchRule( other ),
00212     mBmHeaderField( 0 )
00213 {
00214   if ( other.mBmHeaderField )
00215     mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
00216 }
00217 
00218 const KMSearchRuleString & KMSearchRuleString::operator=( const KMSearchRuleString & other )
00219 {
00220   if ( this == &other )
00221     return *this;
00222 
00223   setField( other.field() );
00224   mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
00225   setFunction( other.function() );
00226   setContents( other.contents() );
00227   delete mBmHeaderField; mBmHeaderField = 0;
00228   if ( other.mBmHeaderField )
00229     mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
00230 
00231   return *this;
00232 }
00233 
00234 KMSearchRuleString::~KMSearchRuleString()
00235 {
00236   delete mBmHeaderField;
00237   mBmHeaderField = 0;
00238 }
00239 
00240 bool KMSearchRuleString::isEmpty() const
00241 {
00242   return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
00243 }
00244 
00245 bool KMSearchRuleString::requiresBody() const
00246 {
00247   if (mBmHeaderField || (field() == "<recipients>" ))
00248     return false;
00249   return true;
00250 }
00251 
00252 bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg,
00253                        const DwBoyerMoore * aHeaderField, int aHeaderLen ) const
00254 {
00255   if ( isEmpty() )
00256     return false;
00257 
00258   bool rc = false;
00259 
00260   const DwBoyerMoore * headerField = aHeaderField ? aHeaderField : mBmHeaderField ;
00261 
00262   const int headerLen = ( aHeaderLen > -1 ? aHeaderLen : field().length() ) + 2 ; // +1 for ': '
00263 
00264   if ( headerField ) {
00265     static const DwBoyerMoore lflf( "\n\n" );
00266     static const DwBoyerMoore lfcrlf( "\n\r\n" );
00267 
00268     size_t endOfHeader = lflf.FindIn( aStr, 0 );
00269     if ( endOfHeader == DwString::npos )
00270       endOfHeader = lfcrlf.FindIn( aStr, 0 );
00271     const DwString headers = ( endOfHeader == DwString::npos ) ? aStr : aStr.substr( 0, endOfHeader );
00272     // In case the searched header is at the beginning, we have to prepend
00273     // a newline - see the comment in KMSearchRuleString constructor
00274     DwString fakedHeaders( "\n" );
00275     size_t start = headerField->FindIn( fakedHeaders.append( headers ), 0, false );
00276     // if the header field doesn't exist then return false for positive
00277     // functions and true for negated functions (e.g. "does not
00278     // contain"); note that all negated string functions correspond
00279     // to an odd value
00280     if ( start == DwString::npos )
00281       rc = ( ( function() & 1 ) == 1 );
00282     else {
00283       start += headerLen;
00284       size_t stop = aStr.find( '\n', start );
00285       char ch = '\0';
00286       while ( stop != DwString::npos && ( ( ch = aStr.at( stop + 1 ) ) == ' ' || ch == '\t' ) )
00287         stop = aStr.find( '\n', stop + 1 );
00288       const int len = stop == DwString::npos ? aStr.length() - start : stop - start ;
00289       const TQCString codedValue( aStr.data() + start, len + 1 );
00290       const TQString msgContents = KMMsgBase::decodeRFC2047String( codedValue ).stripWhiteSpace(); // FIXME: This needs to be changed for IDN support.
00291       rc = matchesInternal( msgContents );
00292     }
00293   } else if ( field() == "<recipients>" ) {
00294     static const DwBoyerMoore to("\nTo: ");
00295     static const DwBoyerMoore cc("\nCc: ");
00296     static const DwBoyerMoore bcc("\nBcc: ");
00297     // <recipients> "contains" "foo" is true if any of the fields contains
00298     // "foo", while <recipients> "does not contain" "foo" is true if none
00299     // of the fields contains "foo"
00300     if ( ( function() & 1 ) == 0 ) {
00301       // positive function, e.g. "contains"
00302       rc = ( matches( aStr, msg, &to, 2 ) ||
00303              matches( aStr, msg, &cc, 2 ) ||
00304              matches( aStr, msg, &bcc, 3 ) );
00305     }
00306     else {
00307       // negated function, e.g. "does not contain"
00308       rc = ( matches( aStr, msg, &to, 2 ) &&
00309              matches( aStr, msg, &cc, 2 ) &&
00310              matches( aStr, msg, &bcc, 3 ) );
00311     }
00312   }
00313   if ( FilterLog::instance()->isLogging() ) {
00314     TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
00315                        : "<font color=#FF0000>0 = </font>" );
00316     msg += FilterLog::recode( asString() );
00317     // only log headers bcause messages and bodies can be pretty large
00318 // FIXME We have to separate the text which is used for filtering to be able to show it in the log
00319 //    if ( logContents )
00320 //      msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
00321     FilterLog::instance()->add( msg, FilterLog::ruleResult );
00322   }
00323   return rc;
00324 }
00325 
00326 bool KMSearchRuleString::matches( const KMMessage * msg ) const
00327 {
00328   assert( msg );
00329 
00330   if ( isEmpty() )
00331     return false;
00332 
00333   TQString msgContents;
00334   // Show the value used to compare the rules against in the log.
00335   // Overwrite the value for complete messages and all headers!
00336   bool logContents = true;
00337 
00338   if( field() == "<message>" ) {
00339 
00340     // When searching in the complete message, we can't simply use msg->asString() here,
00341     // as that wouldn't decode the body. Therefore we use the decoded body and all decoded
00342     // header fields and add all to the one big search string.
00343     msgContents += msg->bodyToUnicode();
00344     const DwHeaders& headers = msg->headers();
00345     const DwField * dwField = headers.FirstField();
00346     while( dwField != 0 ) {
00347       const char * const fieldName = dwField->FieldNameStr().c_str();
00348       const TQString fieldValue = msg->headerFields( fieldName ).join( " " );
00349       msgContents += " " + fieldValue;
00350       dwField = dwField->Next();
00351     }
00352     logContents = false;
00353   } else if ( field() == "<body>" ) {
00354     msgContents = msg->bodyToUnicode();
00355     logContents = false;
00356   } else if ( field() == "<any header>" ) {
00357     msgContents = msg->headerAsString();
00358     logContents = false;
00359   } else if ( field() == "<recipients>" ) {
00360     // (mmutz 2001-11-05) hack to fix "<recipients> !contains foo" to
00361     // meet user's expectations. See FAQ entry in KDE 2.2.2's KMail
00362     // handbook
00363     if ( function() == FuncEquals || function() == FuncNotEqual )
00364       // do we need to treat this case specially? Ie.: What shall
00365       // "equality" mean for recipients.
00366       return matchesInternal( msg->headerField("To") )
00367           || matchesInternal( msg->headerField("Cc") )
00368           || matchesInternal( msg->headerField("Bcc") )
00369           // sometimes messages have multiple Cc headers
00370           || matchesInternal( msg->cc() );
00371 
00372     msgContents = msg->headerField("To");
00373     if ( !msg->headerField("Cc").compare( msg->cc() ) )
00374       msgContents += ", " + msg->headerField("Cc");
00375     else
00376       msgContents += ", " + msg->cc();
00377     msgContents += ", " + msg->headerField("Bcc");
00378   }  else {
00379     // make sure to treat messages with multiple header lines for
00380     // the same header correctly
00381     msgContents = msg->headerFields( field() ).join( " " );
00382   }
00383 
00384   if ( function() == FuncIsInAddressbook ||
00385        function() == FuncIsNotInAddressbook ) {
00386     // I think only the "from"-field makes sense.
00387     msgContents = msg->headerField( field() );
00388     if ( msgContents.isEmpty() )
00389       return ( function() == FuncIsInAddressbook ) ? false : true;
00390   }
00391 
00392   // these two functions need the kmmessage therefore they don't call matchesInternal
00393   if ( function() == FuncHasAttachment )
00394     return ( msg->toMsgBase().attachmentState() == KMMsgHasAttachment );
00395   if ( function() == FuncHasNoAttachment )
00396     return ( ((KMMsgAttachmentState) msg->toMsgBase().attachmentState()) == KMMsgHasNoAttachment );
00397 
00398   bool rc = matchesInternal( msgContents );
00399   if ( FilterLog::instance()->isLogging() ) {
00400     TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
00401                        : "<font color=#FF0000>0 = </font>" );
00402     msg += FilterLog::recode( asString() );
00403     // only log headers bcause messages and bodies can be pretty large
00404     if ( logContents )
00405       msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
00406     FilterLog::instance()->add( msg, FilterLog::ruleResult );
00407   }
00408   return rc;
00409 }
00410 
00411 // helper, does the actual comparing
00412 bool KMSearchRuleString::matchesInternal( const TQString & msgContents ) const
00413 {
00414   switch ( function() ) {
00415   case KMSearchRule::FuncEquals:
00416       return ( TQString::compare( msgContents.lower(), contents().lower() ) == 0 );
00417 
00418   case KMSearchRule::FuncNotEqual:
00419       return ( TQString::compare( msgContents.lower(), contents().lower() ) != 0 );
00420 
00421   case KMSearchRule::FuncContains:
00422     return ( msgContents.find( contents(), 0, false ) >= 0 );
00423 
00424   case KMSearchRule::FuncContainsNot:
00425     return ( msgContents.find( contents(), 0, false ) < 0 );
00426 
00427   case KMSearchRule::FuncRegExp:
00428     {
00429       TQRegExp regexp( contents(), false );
00430       return ( regexp.search( msgContents ) >= 0 );
00431     }
00432 
00433   case KMSearchRule::FuncNotRegExp:
00434     {
00435       TQRegExp regexp( contents(), false );
00436       return ( regexp.search( msgContents ) < 0 );
00437     }
00438 
00439   case FuncIsGreater:
00440       return ( TQString::compare( msgContents.lower(), contents().lower() ) > 0 );
00441 
00442   case FuncIsLessOrEqual:
00443       return ( TQString::compare( msgContents.lower(), contents().lower() ) <= 0 );
00444 
00445   case FuncIsLess:
00446       return ( TQString::compare( msgContents.lower(), contents().lower() ) < 0 );
00447 
00448   case FuncIsGreaterOrEqual:
00449       return ( TQString::compare( msgContents.lower(), contents().lower() ) >= 0 );
00450 
00451   case FuncIsInAddressbook: {
00452     KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
00453     TQStringList addressList =
00454       KPIM::splitEmailAddrList( msgContents.lower() );
00455     for( TQStringList::ConstIterator it = addressList.begin();
00456          ( it != addressList.end() );
00457          ++it ) {
00458       if ( !stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
00459         return true;
00460     }
00461     return false;
00462   }
00463 
00464   case FuncIsNotInAddressbook: {
00465     KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
00466     TQStringList addressList =
00467       KPIM::splitEmailAddrList( msgContents.lower() );
00468     for( TQStringList::ConstIterator it = addressList.begin();
00469          ( it != addressList.end() );
00470          ++it ) {
00471       if ( stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
00472         return true;
00473     }
00474     return false;
00475   }
00476 
00477   case FuncIsInCategory: {
00478     TQString category = contents();
00479     TQStringList addressList =  KPIM::splitEmailAddrList( msgContents.lower() );
00480     KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
00481 
00482     for( TQStringList::ConstIterator it = addressList.begin();
00483       it != addressList.end(); ++it ) {
00484         KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
00485 
00486           for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
00487               if ( (*itAd).hasCategory(category) )
00488                 return true;
00489 
00490       }
00491       return false;
00492     }
00493 
00494     case FuncIsNotInCategory: {
00495       TQString category = contents();
00496       TQStringList addressList =  KPIM::splitEmailAddrList( msgContents.lower() );
00497       KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
00498 
00499       for( TQStringList::ConstIterator it = addressList.begin();
00500         it != addressList.end(); ++it ) {
00501           KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
00502 
00503             for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
00504                 if ( (*itAd).hasCategory(category) )
00505                   return false;
00506 
00507       }
00508       return true;
00509     }
00510   default:
00511     ;
00512   }
00513 
00514   return false;
00515 }
00516 
00517 
00518 //==================================================
00519 //
00520 // class KMSearchRuleNumerical
00521 //
00522 //==================================================
00523 
00524 KMSearchRuleNumerical::KMSearchRuleNumerical( const TQCString & field,
00525                                         Function func, const TQString & contents )
00526           : KMSearchRule(field, func, contents)
00527 {
00528 }
00529 
00530 bool KMSearchRuleNumerical::isEmpty() const
00531 {
00532   bool ok = false;
00533   contents().toInt( &ok );
00534 
00535   return !ok;
00536 }
00537 
00538 
00539 bool KMSearchRuleNumerical::matches( const KMMessage * msg ) const
00540 {
00541 
00542   TQString msgContents;
00543   int numericalMsgContents = 0;
00544   int numericalValue = 0;
00545 
00546   if ( field() == "<size>" ) {
00547     numericalMsgContents = int( msg->msgLength() );
00548     numericalValue = contents().toInt();
00549     msgContents.setNum( numericalMsgContents );
00550   } else if ( field() == "<age in days>" ) {
00551     TQDateTime msgDateTime;
00552     msgDateTime.setTime_t( msg->date() );
00553     numericalMsgContents = msgDateTime.daysTo( TQDateTime::currentDateTime() );
00554     numericalValue = contents().toInt();
00555     msgContents.setNum( numericalMsgContents );
00556   }
00557   bool rc = matchesInternal( numericalValue, numericalMsgContents, msgContents );
00558   if ( FilterLog::instance()->isLogging() ) {
00559     TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
00560                        : "<font color=#FF0000>0 = </font>" );
00561     msg += FilterLog::recode( asString() );
00562     msg += " ( <i>" + TQString::number( numericalMsgContents ) + "</i> )";
00563     FilterLog::instance()->add( msg, FilterLog::ruleResult );
00564   }
00565   return rc;
00566 }
00567 
00568 bool KMSearchRuleNumerical::matchesInternal( long numericalValue,
00569     long numericalMsgContents, const TQString & msgContents ) const
00570 {
00571   switch ( function() ) {
00572   case KMSearchRule::FuncEquals:
00573       return ( numericalValue == numericalMsgContents );
00574 
00575   case KMSearchRule::FuncNotEqual:
00576       return ( numericalValue != numericalMsgContents );
00577 
00578   case KMSearchRule::FuncContains:
00579     return ( msgContents.find( contents(), 0, false ) >= 0 );
00580 
00581   case KMSearchRule::FuncContainsNot:
00582     return ( msgContents.find( contents(), 0, false ) < 0 );
00583 
00584   case KMSearchRule::FuncRegExp:
00585     {
00586       TQRegExp regexp( contents(), false );
00587       return ( regexp.search( msgContents ) >= 0 );
00588     }
00589 
00590   case KMSearchRule::FuncNotRegExp:
00591     {
00592       TQRegExp regexp( contents(), false );
00593       return ( regexp.search( msgContents ) < 0 );
00594     }
00595 
00596   case FuncIsGreater:
00597       return ( numericalMsgContents > numericalValue );
00598 
00599   case FuncIsLessOrEqual:
00600       return ( numericalMsgContents <= numericalValue );
00601 
00602   case FuncIsLess:
00603       return ( numericalMsgContents < numericalValue );
00604 
00605   case FuncIsGreaterOrEqual:
00606       return ( numericalMsgContents >= numericalValue );
00607 
00608   case FuncIsInAddressbook:  // since email-addresses are not numerical, I settle for false here
00609     return false;
00610 
00611   case FuncIsNotInAddressbook:
00612     return false;
00613 
00614   default:
00615     ;
00616   }
00617 
00618   return false;
00619 }
00620 
00621 
00622 
00623 //==================================================
00624 //
00625 // class KMSearchRuleStatus
00626 //
00627 //==================================================
00628 TQString englishNameForStatus( const KMMsgStatus& status )
00629 {
00630   for ( int i=0; i< numStatusNames; i++ ) {
00631     if ( statusNames[i].status == status ) {
00632       return statusNames[i].name;
00633     }
00634   }
00635   return TQString();
00636 }
00637 
00638 KMSearchRuleStatus::KMSearchRuleStatus( const TQCString & field,
00639                                         Function func, const TQString & aContents )
00640           : KMSearchRule(field, func, aContents)
00641 {
00642   // the values are always in english, both from the conf file as well as
00643   // the patternedit gui
00644   mStatus = statusFromEnglishName( aContents );
00645 }
00646 
00647 KMSearchRuleStatus::KMSearchRuleStatus( int status, Function func )
00648 : KMSearchRule( "<status>", func, englishNameForStatus( status ) )
00649 {
00650     mStatus = status;
00651 }
00652 
00653 KMMsgStatus KMSearchRuleStatus::statusFromEnglishName( const TQString & aStatusString )
00654 {
00655   for ( int i=0; i< numStatusNames; i++ ) {
00656     if ( !aStatusString.compare( statusNames[i].name ) ) {
00657       return statusNames[i].status;
00658     }
00659   }
00660   return KMMsgStatusUnknown;
00661 }
00662 
00663 bool KMSearchRuleStatus::isEmpty() const
00664 {
00665   return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
00666 }
00667 
00668 bool KMSearchRuleStatus::matches( const DwString &, KMMessage &,
00669                   const DwBoyerMoore *, int ) const
00670 {
00671   assert( 0 );
00672   return false; // don't warn
00673 }
00674 
00675 bool KMSearchRuleStatus::matches( const KMMessage * msg ) const
00676 {
00677 
00678   KMMsgStatus msgStatus = msg->status();
00679   bool rc = false;
00680 
00681   switch ( function() ) {
00682     case FuncEquals: // fallthrough. So that "<status> 'is' 'read'" works
00683     case FuncContains:
00684       if (msgStatus & mStatus)
00685         rc = true;
00686       break;
00687     case FuncNotEqual: // fallthrough. So that "<status> 'is not' 'read'" works
00688     case FuncContainsNot:
00689       if (! (msgStatus & mStatus) )
00690         rc = true;
00691       break;
00692     // FIXME what about the remaining funcs, how can they make sense for
00693     // stati?
00694     default:
00695       break;
00696   }
00697 
00698   if ( FilterLog::instance()->isLogging() ) {
00699     TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
00700                        : "<font color=#FF0000>0 = </font>" );
00701     msg += FilterLog::recode( asString() );
00702     FilterLog::instance()->add( msg, FilterLog::ruleResult );
00703   }
00704   return rc;
00705 }
00706 
00707 // ----------------------------------------------------------------------------
00708 
00709 //==================================================
00710 //
00711 // class KMSearchPattern
00712 //
00713 //==================================================
00714 
00715 KMSearchPattern::KMSearchPattern( const KConfig * config )
00716   : TQPtrList<KMSearchRule>()
00717 {
00718   setAutoDelete( true );
00719   if ( config )
00720     readConfig( config );
00721   else
00722     init();
00723 }
00724 
00725 KMSearchPattern::~KMSearchPattern()
00726 {
00727 }
00728 
00729 bool KMSearchPattern::matches( const KMMessage * msg, bool ignoreBody ) const
00730 {
00731   if ( isEmpty() )
00732     return true;
00733 
00734   TQPtrListIterator<KMSearchRule> it( *this );
00735   switch ( mOperator ) {
00736   case OpAnd: // all rules must match
00737     for ( it.toFirst() ; it.current() ; ++it )
00738       if ( !((*it)->requiresBody() && ignoreBody) )
00739         if ( !(*it)->matches( msg ) )
00740           return false;
00741     return true;
00742   case OpOr:  // at least one rule must match
00743     for ( it.toFirst() ; it.current() ; ++it )
00744       if ( !((*it)->requiresBody() && ignoreBody) )
00745         if ( (*it)->matches( msg ) )
00746           return true;
00747     // fall through
00748   default:
00749     return false;
00750   }
00751 }
00752 
00753 bool KMSearchPattern::matches( const DwString & aStr, bool ignoreBody ) const
00754 {
00755   if ( isEmpty() )
00756     return true;
00757 
00758   KMMessage msg;
00759   TQPtrListIterator<KMSearchRule> it( *this );
00760   switch ( mOperator ) {
00761   case OpAnd: // all rules must match
00762     for ( it.toFirst() ; it.current() ; ++it )
00763       if ( !((*it)->requiresBody() && ignoreBody) )
00764         if ( !(*it)->matches( aStr, msg ) )
00765           return false;
00766     return true;
00767   case OpOr:  // at least one rule must match
00768     for ( it.toFirst() ; it.current() ; ++it )
00769       if ( !((*it)->requiresBody() && ignoreBody) )
00770         if ( (*it)->matches( aStr, msg ) )
00771           return true;
00772     // fall through
00773   default:
00774     return false;
00775   }
00776 }
00777 
00778 bool KMSearchPattern::matches( TQ_UINT32 serNum, bool ignoreBody ) const
00779 {
00780   if ( isEmpty() )
00781     return true;
00782 
00783   bool res;
00784   int idx = -1;
00785   KMFolder *folder = 0;
00786   KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
00787   if (!folder || (idx == -1) || (idx >= folder->count())) {
00788     return false;
00789   }
00790 
00791   KMFolderOpener openFolder(folder, "searchptr");
00792   KMMsgBase *msgBase = folder->getMsgBase(idx);
00793   if (requiresBody() && !ignoreBody) {
00794     bool unGet = !msgBase->isMessage();
00795     KMMessage *msg = folder->getMsg(idx);
00796     res = false;
00797     if ( msg ) {
00798       res = matches( msg, ignoreBody );
00799       if (unGet)
00800         folder->unGetMsg(idx);
00801     }
00802   } else {
00803     res = matches( folder->getDwString(idx), ignoreBody );
00804   }
00805   return res;
00806 }
00807 
00808 bool KMSearchPattern::requiresBody() const {
00809   TQPtrListIterator<KMSearchRule> it( *this );
00810     for ( it.toFirst() ; it.current() ; ++it )
00811       if ( (*it)->requiresBody() )
00812     return true;
00813   return false;
00814 }
00815 
00816 void KMSearchPattern::purify() {
00817   TQPtrListIterator<KMSearchRule> it( *this );
00818   it.toLast();
00819   while ( it.current() )
00820     if ( (*it)->isEmpty() ) {
00821 #ifndef NDEBUG
00822       kdDebug(5006) << "KMSearchPattern::purify(): removing " << (*it)->asString() << endl;
00823 #endif
00824       remove( *it );
00825     } else {
00826       --it;
00827     }
00828 }
00829 
00830 void KMSearchPattern::readConfig( const KConfig * config ) {
00831   init();
00832 
00833   mName = config->readEntry("name");
00834   if ( !config->hasKey("rules") ) {
00835     kdDebug(5006) << "KMSearchPattern::readConfig: found legacy config! Converting." << endl;
00836     importLegacyConfig( config );
00837     return;
00838   }
00839 
00840   mOperator = config->readEntry("operator") == "or" ? OpOr : OpAnd;
00841 
00842   const int nRules = config->readNumEntry( "rules", 0 );
00843 
00844   for ( int i = 0 ; i < nRules ; i++ ) {
00845     KMSearchRule * r = KMSearchRule::createInstanceFromConfig( config, i );
00846     if ( r->isEmpty() )
00847       delete r;
00848     else
00849       append( r );
00850   }
00851 }
00852 
00853 void KMSearchPattern::importLegacyConfig( const KConfig * config ) {
00854   KMSearchRule * rule = KMSearchRule::createInstance( config->readEntry("fieldA").latin1(),
00855                       config->readEntry("funcA").latin1(),
00856                       config->readEntry("contentsA") );
00857   if ( rule->isEmpty() ) {
00858     // if the first rule is invalid,
00859     // we really can't do much heuristics...
00860     delete rule;
00861     return;
00862   }
00863   append( rule );
00864 
00865   const TQString sOperator = config->readEntry("operator");
00866   if ( sOperator == "ignore" ) return;
00867 
00868   rule = KMSearchRule::createInstance( config->readEntry("fieldB").latin1(),
00869                config->readEntry("funcB").latin1(),
00870                config->readEntry("contentsB") );
00871   if ( rule->isEmpty() ) {
00872     delete rule;
00873     return;
00874   }
00875   append( rule );
00876 
00877   if ( sOperator == "or"  ) {
00878     mOperator = OpOr;
00879     return;
00880   }
00881   // This is the interesting case...
00882   if ( sOperator == "unless" ) { // meaning "and not", ie we need to...
00883     // ...invert the function (e.g. "equals" <-> "doesn't equal")
00884     // We simply toggle the last bit (xor with 0x1)... This assumes that
00885     // KMSearchRule::Function's come in adjacent pairs of pros and cons
00886     KMSearchRule::Function func = last()->function();
00887     unsigned int intFunc = (unsigned int)func;
00888     func = KMSearchRule::Function( intFunc ^ 0x1 );
00889 
00890     last()->setFunction( func );
00891   }
00892 
00893   // treat any other case as "and" (our default).
00894 }
00895 
00896 void KMSearchPattern::writeConfig( KConfig * config ) const {
00897   config->writeEntry("name", mName);
00898   config->writeEntry("operator", (mOperator == KMSearchPattern::OpOr) ? "or" : "and" );
00899 
00900   int i = 0;
00901   for ( TQPtrListIterator<KMSearchRule> it( *this ) ; it.current() && i < FILTER_MAX_RULES ; ++i , ++it )
00902     // we could do this ourselves, but we want the rules to be extensible,
00903     // so we give the rule it's number and let it do the rest.
00904     (*it)->writeConfig( config, i );
00905 
00906   // save the total number of rules.
00907   config->writeEntry( "rules", i );
00908 }
00909 
00910 void KMSearchPattern::init() {
00911   clear();
00912   mOperator = OpAnd;
00913   mName = '<' + i18n("name used for a virgin filter","unknown") + '>';
00914 }
00915 
00916 TQString KMSearchPattern::asString() const {
00917   TQString result;
00918   if ( mOperator == OpOr )
00919     result = i18n("(match any of the following)");
00920   else
00921     result = i18n("(match all of the following)");
00922 
00923   for ( TQPtrListIterator<KMSearchRule> it( *this ) ; it.current() ; ++it )
00924     result += "\n\t" + FilterLog::recode( (*it)->asString() );
00925 
00926   return result;
00927 }
00928 
00929 const KMSearchPattern & KMSearchPattern::operator=( const KMSearchPattern & other ) {
00930   if ( this == &other )
00931     return *this;
00932 
00933   setOp( other.op() );
00934   setName( other.name() );
00935 
00936   clear(); // ###
00937 
00938   for ( TQPtrListIterator<KMSearchRule> it( other ) ; it.current() ; ++it ) {
00939     KMSearchRule * rule = KMSearchRule::createInstance( **it ); // deep copy
00940     append( rule );
00941   }
00942 
00943   return *this;
00944 }