00001
00002
00003
00004
00005 #include <config.h>
00006
00007 #include "kmsearchpattern.h"
00008 #include "kmmessage.h"
00009 #include "kmmsgindex.h"
00010 #include "kmmsgdict.h"
00011
00012 #include <kglobal.h>
00013 #include <klocale.h>
00014 #include <kdebug.h>
00015 #include <kconfig.h>
00016
00017 #include <qregexp.h>
00018
00019 #include <mimelib/string.h>
00020 #include <mimelib/boyermor.h>
00021
00022 #include <assert.h>
00023
00024 static const char* funcConfigNames[] =
00025 { "contains", "contains-not", "equals", "not-equal", "regexp",
00026 "not-regexp", "greater", "less-or-equal", "less", "greater-or-equal" };
00027 static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames;
00028
00029
00030
00031
00032
00033
00034
00035
00036 KMSearchRule::KMSearchRule( const QCString & field, Function func, const QString & contents )
00037 : mField( field ),
00038 mFunction( func ),
00039 mContents( contents )
00040 {
00041 }
00042
00043 KMSearchRule::KMSearchRule( const KMSearchRule & other )
00044 : mField( other.mField ),
00045 mFunction( other.mFunction ),
00046 mContents( other.mContents )
00047 {
00048 }
00049
00050 const KMSearchRule & KMSearchRule::operator=( const KMSearchRule & other ) {
00051 if ( this == &other )
00052 return *this;
00053
00054 mField = other.mField;
00055 mFunction = other.mFunction;
00056 mContents = other.mContents;
00057
00058 return *this;
00059 }
00060
00061 KMSearchRule * KMSearchRule::createInstance( const QCString & field,
00062 Function func,
00063 const QString & contents )
00064 {
00065 KMSearchRule *ret;
00066 if (field == "<status>")
00067 ret = new KMSearchRuleStatus( field, func, contents );
00068 else if ( field == "<age in days>" || field == "<size>" )
00069 ret = new KMSearchRuleNumerical( field, func, contents );
00070 else
00071 ret = new KMSearchRuleString( field, func, contents );
00072
00073 return ret;
00074 }
00075
00076 KMSearchRule * KMSearchRule::createInstance( const QCString & field,
00077 const char *func,
00078 const QString & contents )
00079 {
00080 return ( createInstance( field, configValueToFunc( func ), contents ) );
00081 }
00082
00083 KMSearchRule * KMSearchRule::createInstance( const KMSearchRule & other )
00084 {
00085 return ( createInstance( other.field(), other.function(), other.contents() ) );
00086 }
00087
00088 KMSearchRule * KMSearchRule::createInstanceFromConfig( const KConfig * config, int aIdx )
00089 {
00090 const char cIdx = char( int('A') + aIdx );
00091
00092 static const QString & field = KGlobal::staticQString( "field" );
00093 static const QString & func = KGlobal::staticQString( "func" );
00094 static const QString & contents = KGlobal::staticQString( "contents" );
00095
00096 const QCString &field2 = config->readEntry( field + cIdx ).latin1();
00097 Function func2 = configValueToFunc( config->readEntry( func + cIdx ).latin1() );
00098 const QString & contents2 = config->readEntry( contents + cIdx );
00099
00100 if ( field2 == "<To or Cc>" )
00101 return KMSearchRule::createInstance( "<recipients>", func2, contents2 );
00102 else
00103 return KMSearchRule::createInstance( field2, func2, contents2 );
00104 }
00105
00106 KMSearchRule::Function KMSearchRule::configValueToFunc( const char * str ) {
00107 if ( !str ) return FuncContains;
00108
00109 for ( int i = 0 ; i < numFuncConfigNames ; ++i )
00110 if ( qstricmp( funcConfigNames[i], str ) == 0 ) return (Function)i;
00111
00112 return FuncContains;
00113 }
00114
00115 void KMSearchRule::writeConfig( KConfig * config, int aIdx ) const {
00116 const char cIdx = char('A' + aIdx);
00117 static const QString & field = KGlobal::staticQString( "field" );
00118 static const QString & func = KGlobal::staticQString( "func" );
00119 static const QString & contents = KGlobal::staticQString( "contents" );
00120
00121 config->writeEntry( field + cIdx, QString(mField) );
00122 config->writeEntry( func + cIdx, funcConfigNames[(int)mFunction] );
00123 config->writeEntry( contents + cIdx, mContents );
00124 }
00125
00126 bool KMSearchRule::matches( const DwString & aStr, KMMessage & msg,
00127 const DwBoyerMoore *, int ) const
00128 {
00129 if ( !msg.isComplete() ) {
00130 msg.fromDwString( aStr );
00131 msg.setComplete( true );
00132 }
00133 return matches( &msg );
00134 }
00135
00136 #ifndef NDEBUG
00137 const QString KMSearchRule::asString() const
00138 {
00139 QString result = "\"" + mField + "\" <";
00140 result += funcConfigNames[(int)mFunction];
00141 result += "> \"" + mContents + "\"";
00142
00143 return result;
00144 }
00145 #endif
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155 KMSearchRuleString::KMSearchRuleString( const QCString & field,
00156 Function func, const QString & contents )
00157 : KMSearchRule(field, func, contents)
00158 {
00159 if ( field.isEmpty() || field[0] == '<' )
00160 mBmHeaderField = 0;
00161 else
00162 mBmHeaderField = new DwBoyerMoore(("\n" + field + ": ").data());
00163 }
00164
00165 KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString & other )
00166 : KMSearchRule( other ),
00167 mBmHeaderField( 0 )
00168 {
00169 if ( other.mBmHeaderField )
00170 mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
00171 }
00172
00173 const KMSearchRuleString & KMSearchRuleString::operator=( const KMSearchRuleString & other )
00174 {
00175 if ( this == &other )
00176 return *this;
00177
00178 setField( other.field() );
00179 mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
00180 setFunction( other.function() );
00181 setContents( other.contents() );
00182 delete mBmHeaderField; mBmHeaderField = 0;
00183 if ( other.mBmHeaderField )
00184 mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
00185
00186 return *this;
00187 }
00188
00189 KMSearchRuleString::~KMSearchRuleString()
00190 {
00191 delete mBmHeaderField;
00192 mBmHeaderField = 0;
00193 }
00194
00195 bool KMSearchRuleString::isEmpty() const
00196 {
00197 return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
00198 }
00199
00200 bool KMSearchRuleString::requiresBody() const
00201 {
00202 if (mBmHeaderField || (field() == "<recipients>" ))
00203 return false;
00204 return true;
00205 }
00206
00207 bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg,
00208 const DwBoyerMoore * aHeaderField, int aHeaderLen ) const
00209 {
00210 if ( isEmpty() )
00211 return false;
00212
00213 const DwBoyerMoore * headerField = aHeaderField ? aHeaderField : mBmHeaderField ;
00214
00215 const int headerLen = ( aHeaderLen > -1 ? aHeaderLen : field().length() ) + 2 ;
00216
00217 if ( headerField ) {
00218 static const DwBoyerMoore lf( "\n" );
00219 static const DwBoyerMoore lflf( "\n\n" );
00220 static const DwBoyerMoore lfcrlf( "\n\r\n" );
00221
00222 size_t endOfHeader = lflf.FindIn( aStr, 0 );
00223 if ( endOfHeader == DwString::npos )
00224 endOfHeader = lfcrlf.FindIn( aStr, 0 );
00225 const DwString headers = ( endOfHeader == DwString::npos ) ? aStr : aStr.substr( 0, endOfHeader );
00226 size_t start = headerField->FindIn( headers, 0, false );
00227 if ( start == DwString::npos )
00228 return false;
00229 start += headerLen;
00230 size_t stop = lf.FindIn( aStr, start );
00231 char ch = '\0';
00232 while ( stop != DwString::npos && ( ch = aStr.at( stop + 1 ) ) == ' ' || ch == '\t' )
00233 stop = lf.FindIn( aStr, stop + 1 );
00234 const int len = stop == DwString::npos ? aStr.length() - start : stop - start ;
00235 const QCString codedValue( aStr.data() + start, len + 1 );
00236 const QString msgContents = KMMsgBase::decodeRFC2047String( codedValue ).stripWhiteSpace();
00237 return matchesInternal( msgContents );
00238 } else if ( field() == "<recipients>" ) {
00239 static const DwBoyerMoore to("\nTo: ");
00240 bool res = matches( aStr, msg, &to, 2 );
00241 if ( !res ) {
00242 static const DwBoyerMoore cc("\nCc: ");
00243 res = matches( aStr, msg, &cc, 2 );
00244 }
00245 if ( !res ) {
00246 static const DwBoyerMoore bcc("\nBcc: ");
00247 res = matches( aStr, msg, &bcc, 3 );
00248 }
00249 return res;
00250 } else {
00251 return KMSearchRule::matches( aStr, msg, aHeaderField, aHeaderLen );
00252 }
00253 }
00254
00255 bool KMSearchRuleString::matches( const KMMessage * msg ) const
00256 {
00257 assert( msg );
00258
00259 if ( isEmpty() )
00260 return false;
00261
00262 QString msgContents;
00263
00264 if( field() == "<message>" ) {
00265 msgContents = msg->asString();
00266 } else if ( field() == "<body>" ) {
00267 msgContents = msg->bodyDecoded();
00268 } else if ( field() == "<any header>" ) {
00269 msgContents = msg->headerAsString();
00270 } else if ( field() == "<recipients>" ) {
00271
00272
00273
00274 if ( function() == FuncEquals || function() == FuncNotEqual )
00275
00276
00277 return matchesInternal( msg->headerField("To") )
00278 || matchesInternal( msg->headerField("Cc") )
00279 || matchesInternal( msg->headerField("Bcc") );
00280
00281 msgContents = msg->headerField("To") + msg->headerField("Cc")
00282 + msg->headerField("Bcc");
00283 } else {
00284 msgContents = msg->headerField( field() );
00285 }
00286 return matchesInternal( msgContents );
00287 }
00288
00289
00290 bool KMSearchRuleString::matchesInternal( const QString & msgContents ) const
00291 {
00292 switch ( function() ) {
00293 case KMSearchRule::FuncEquals:
00294 return ( QString::compare( msgContents.lower(), contents().lower() ) == 0 );
00295
00296 case KMSearchRule::FuncNotEqual:
00297 return ( QString::compare( msgContents.lower(), contents().lower() ) != 0 );
00298
00299 case KMSearchRule::FuncContains:
00300 return ( msgContents.find( contents(), 0, false ) >= 0 );
00301
00302 case KMSearchRule::FuncContainsNot:
00303 return ( msgContents.find( contents(), 0, false ) < 0 );
00304
00305 case KMSearchRule::FuncRegExp:
00306 {
00307 QRegExp regexp( contents(), false );
00308 return ( regexp.search( msgContents ) >= 0 );
00309 }
00310
00311 case KMSearchRule::FuncNotRegExp:
00312 {
00313 QRegExp regexp( contents(), false );
00314 return ( regexp.search( msgContents ) < 0 );
00315 }
00316
00317 case FuncIsGreater:
00318 return ( QString::compare( msgContents.lower(), contents().lower() ) > 0 );
00319
00320 case FuncIsLessOrEqual:
00321 return ( QString::compare( msgContents.lower(), contents().lower() ) <= 0 );
00322
00323 case FuncIsLess:
00324 return ( QString::compare( msgContents.lower(), contents().lower() ) < 0 );
00325
00326 case FuncIsGreaterOrEqual:
00327 return ( QString::compare( msgContents.lower(), contents().lower() ) >= 0 );
00328 }
00329
00330 return false;
00331 }
00332
00333
00334
00335
00336
00337
00338
00339
00340 KMSearchRuleNumerical::KMSearchRuleNumerical( const QCString & field,
00341 Function func, const QString & contents )
00342 : KMSearchRule(field, func, contents)
00343 {
00344 }
00345
00346 bool KMSearchRuleNumerical::isEmpty() const
00347 {
00348 bool ok = false;
00349 contents().toInt( &ok );
00350
00351 return !ok;
00352 }
00353
00354
00355 bool KMSearchRuleNumerical::matches( const KMMessage * msg ) const
00356 {
00357
00358 QString msgContents;
00359 int numericalMsgContents = 0;
00360 int numericalValue = 0;
00361
00362 if ( field() == "<size>" ) {
00363 numericalMsgContents = int( msg->msgLength() );
00364 numericalValue = contents().toInt();
00365 msgContents.setNum( numericalMsgContents );
00366 } else if ( field() == "<age in days>" ) {
00367 QDateTime msgDateTime;
00368 msgDateTime.setTime_t( msg->date() );
00369 numericalMsgContents = msgDateTime.daysTo( QDateTime::currentDateTime() );
00370 numericalValue = contents().toInt();
00371 msgContents.setNum( numericalMsgContents );
00372 }
00373 return matchesInternal( numericalValue, numericalMsgContents, msgContents );
00374 }
00375
00376 bool KMSearchRuleNumerical::matchesInternal( unsigned long numericalValue,
00377 unsigned long numericalMsgContents, const QString & msgContents ) const
00378 {
00379 switch ( function() ) {
00380 case KMSearchRule::FuncEquals:
00381 return ( numericalValue == numericalMsgContents );
00382
00383 case KMSearchRule::FuncNotEqual:
00384 return ( numericalValue != numericalMsgContents );
00385
00386 case KMSearchRule::FuncContains:
00387 return ( msgContents.find( contents(), 0, false ) >= 0 );
00388
00389 case KMSearchRule::FuncContainsNot:
00390 return ( msgContents.find( contents(), 0, false ) < 0 );
00391
00392 case KMSearchRule::FuncRegExp:
00393 {
00394 QRegExp regexp( contents(), false );
00395 return ( regexp.search( msgContents ) >= 0 );
00396 }
00397
00398 case KMSearchRule::FuncNotRegExp:
00399 {
00400 QRegExp regexp( contents(), false );
00401 return ( regexp.search( msgContents ) < 0 );
00402 }
00403
00404 case FuncIsGreater:
00405 return ( numericalMsgContents > numericalValue );
00406
00407 case FuncIsLessOrEqual:
00408 return ( numericalMsgContents <= numericalValue );
00409
00410 case FuncIsLess:
00411 return ( numericalMsgContents < numericalValue );
00412
00413 case FuncIsGreaterOrEqual:
00414 return ( numericalMsgContents >= numericalValue );
00415 }
00416
00417 return false;
00418 }
00419
00420
00421
00422
00423
00424
00425
00426
00427
00428 KMSearchRuleStatus::KMSearchRuleStatus( const QCString & field,
00429 Function func, const QString & aContents )
00430 : KMSearchRule(field, func, aContents)
00431 {
00432
00433
00434 if ( ! aContents.compare("new") )
00435 mStatus = KMMsgStatusNew;
00436 if ( ! aContents.compare("unread") )
00437 mStatus = KMMsgStatusUnread;
00438 if ( ! aContents.compare("read") )
00439 mStatus = KMMsgStatusRead;
00440 if ( ! aContents.compare("old") )
00441 mStatus = KMMsgStatusOld;
00442 if ( ! aContents.compare("deleted") )
00443 mStatus = KMMsgStatusDeleted;
00444 if ( ! aContents.compare("replied") )
00445 mStatus = KMMsgStatusReplied;
00446 if ( ! aContents.compare("forwarded") )
00447 mStatus = KMMsgStatusForwarded;
00448 if ( ! aContents.compare("queued") )
00449 mStatus = KMMsgStatusQueued;
00450 if ( ! aContents.compare("sent") )
00451 mStatus = KMMsgStatusSent;
00452 if ( ! aContents.compare("important") )
00453 mStatus = KMMsgStatusFlag;
00454 if ( ! aContents.compare("watched") )
00455 mStatus = KMMsgStatusWatched;
00456 if ( ! aContents.compare("ignored") )
00457 mStatus = KMMsgStatusIgnored;
00458
00459
00460
00461
00462 if ( ! aContents.compare("spam") )
00463 mStatus = KMMsgStatusSpam;
00464 if ( ! aContents.compare("ham") )
00465 mStatus = KMMsgStatusHam;
00466 }
00467
00468 bool KMSearchRuleStatus::isEmpty() const
00469 {
00470 return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
00471 }
00472
00473 bool KMSearchRuleStatus::matches( const DwString &, KMMessage &,
00474 const DwBoyerMoore *, int ) const
00475 {
00476 assert( 0 );
00477 return false;
00478 }
00479
00480 bool KMSearchRuleStatus::matches( const KMMessage * msg ) const
00481 {
00482
00483 KMMsgStatus msgStatus = msg->status();
00484
00485 switch ( function() ) {
00486 case FuncEquals:
00487 case FuncContains:
00488 if (msgStatus & mStatus)
00489 return true;
00490 break;
00491 case FuncNotEqual:
00492 case FuncContainsNot:
00493 if (! (msgStatus & mStatus) )
00494 return true;
00495 break;
00496
00497
00498 default:
00499 break;
00500 }
00501
00502 return false;
00503 }
00504
00505
00506
00507
00508
00509
00510
00511
00512
00513 KMSearchPattern::KMSearchPattern( const KConfig * config )
00514 : QPtrList<KMSearchRule>()
00515 {
00516 setAutoDelete( true );
00517 if ( config )
00518 readConfig( config );
00519 else
00520 init();
00521 }
00522
00523 KMSearchPattern::~KMSearchPattern()
00524 {
00525 }
00526
00527 bool KMSearchPattern::matches( const KMMessage * msg ) const {
00528
00529 if ( isEmpty() )
00530 return false;
00531 QPtrListIterator<KMSearchRule> it( *this );
00532 switch ( mOperator ) {
00533 case OpAnd:
00534 for ( it.toFirst() ; it.current() ; ++it )
00535 if ( !(*it)->matches( msg ) )
00536 return false;
00537 return true;
00538 case OpOr:
00539 for ( it.toFirst() ; it.current() ; ++it )
00540 if ( (*it)->matches( msg ) )
00541 return true;
00542
00543 default:
00544 return false;
00545 }
00546 }
00547
00548 bool KMSearchPattern::matches( const DwString & aStr ) const
00549 {
00550 if ( isEmpty() )
00551 return false;
00552 KMMessage msg;
00553 QPtrListIterator<KMSearchRule> it( *this );
00554 switch ( mOperator ) {
00555 case OpAnd:
00556 for ( it.toFirst() ; it.current() ; ++it )
00557 if ( !(*it)->matches( aStr, msg ) )
00558 return false;
00559 return true;
00560 case OpOr:
00561 for ( it.toFirst() ; it.current() ; ++it )
00562 if ( (*it)->matches( aStr, msg ) )
00563 return true;
00564
00565 default:
00566 return false;
00567 }
00568 }
00569
00570 bool KMSearchPattern::matches( Q_UINT32 serNum ) const
00571 {
00572 bool res;
00573 int idx = -1;
00574 KMFolder *folder = 0;
00575 kmkernel->msgDict()->getLocation(serNum, &folder, &idx);
00576 if (!folder || (idx == -1) || (idx >= folder->count())) {
00577 return false;
00578 }
00579
00580 folder->open();
00581 KMMsgBase *msgBase = folder->getMsgBase(idx);
00582 if (requiresBody()) {
00583 bool unGet = !msgBase->isMessage();
00584 KMMessage *msg = folder->getMsg(idx);
00585 res = matches( msg );
00586 if (unGet)
00587 folder->unGetMsg(idx);
00588 } else {
00589 res = matches( folder->getDwString(idx) );
00590 }
00591 folder->close();
00592 return res;
00593 }
00594
00595 bool KMSearchPattern::requiresBody() const {
00596 QPtrListIterator<KMSearchRule> it( *this );
00597 for ( it.toFirst() ; it.current() ; ++it )
00598 if ( (*it)->requiresBody() )
00599 return true;
00600 return false;
00601 }
00602
00603 void KMSearchPattern::purify() {
00604 QPtrListIterator<KMSearchRule> it( *this );
00605 it.toLast();
00606 while ( it.current() )
00607 if ( (*it)->isEmpty() ) {
00608 #ifndef NDEBUG
00609 kdDebug(5006) << "KMSearchPattern::purify(): removing " << (*it)->asString() << endl;
00610 #endif
00611 remove( *it );
00612 } else {
00613 --it;
00614 }
00615 }
00616
00617 void KMSearchPattern::readConfig( const KConfig * config ) {
00618 init();
00619
00620 mName = config->readEntry("name");
00621 if ( !config->hasKey("rules") ) {
00622 kdDebug(5006) << "KMSearchPattern::readConfig: found legacy config! Converting." << endl;
00623 importLegacyConfig( config );
00624 return;
00625 }
00626
00627 mOperator = config->readEntry("operator") == "or" ? OpOr : OpAnd;
00628
00629 const int nRules = config->readNumEntry( "rules", 0 );
00630
00631 for ( int i = 0 ; i < nRules ; i++ ) {
00632 KMSearchRule * r = KMSearchRule::createInstanceFromConfig( config, i );
00633 if ( r->isEmpty() )
00634 delete r;
00635 else
00636 append( r );
00637 }
00638 }
00639
00640 void KMSearchPattern::importLegacyConfig( const KConfig * config ) {
00641 KMSearchRule * rule = KMSearchRule::createInstance( config->readEntry("fieldA").latin1(),
00642 config->readEntry("funcA").latin1(),
00643 config->readEntry("contentsA") );
00644 if ( rule->isEmpty() ) {
00645
00646
00647 delete rule;
00648 return;
00649 }
00650 append( rule );
00651
00652 const QString sOperator = config->readEntry("operator");
00653 if ( sOperator == "ignore" ) return;
00654
00655 rule = KMSearchRule::createInstance( config->readEntry("fieldB").latin1(),
00656 config->readEntry("funcB").latin1(),
00657 config->readEntry("contentsB") );
00658 if ( rule->isEmpty() ) {
00659 delete rule;
00660 return;
00661 }
00662 append( rule );
00663
00664 if ( sOperator == "or" ) {
00665 mOperator = OpOr;
00666 return;
00667 }
00668
00669 if ( sOperator == "unless" ) {
00670
00671
00672
00673 KMSearchRule::Function func = last()->function();
00674 unsigned int intFunc = (unsigned int)func;
00675 func = KMSearchRule::Function( intFunc ^ 0x1 );
00676
00677 last()->setFunction( func );
00678 }
00679
00680
00681 }
00682
00683 void KMSearchPattern::writeConfig( KConfig * config ) const {
00684 config->writeEntry("name", mName);
00685 config->writeEntry("operator", (mOperator == KMSearchPattern::OpOr) ? "or" : "and" );
00686
00687 int i = 0;
00688 for ( QPtrListIterator<KMSearchRule> it( *this ) ; it.current() && i < FILTER_MAX_RULES ; ++i , ++it )
00689
00690
00691 (*it)->writeConfig( config, i );
00692
00693
00694 config->writeEntry( "rules", i );
00695 }
00696
00697 void KMSearchPattern::init() {
00698 clear();
00699 mOperator = OpAnd;
00700 mName = '<' + i18n("name used for a virgin filter","unknown") + '>';
00701 }
00702
00703 #ifndef NDEBUG
00704 QString KMSearchPattern::asString() const {
00705 QString result = "Match ";
00706 result += ( mOperator == OpOr ) ? "any" : "all";
00707 result += " of the following:\n";
00708
00709 for ( QPtrListIterator<KMSearchRule> it( *this ) ; it.current() ; ++it )
00710 result += (*it)->asString() + '\n';
00711
00712 return result;
00713 }
00714 #endif
00715
00716 const KMSearchPattern & KMSearchPattern::operator=( const KMSearchPattern & other ) {
00717 if ( this == &other )
00718 return *this;
00719
00720 setOp( other.op() );
00721 setName( other.name() );
00722
00723 clear();
00724 for ( QPtrListIterator<KMSearchRule> it( other ) ; it.current() ; ++it )
00725 append( KMSearchRule::createInstance( **it ) );
00726
00727 return *this;
00728 }