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 <tdeglobal.h> 00020 #include <tdelocale.h> 00021 #include <kdebug.h> 00022 #include <tdeconfig.h> 00023 00024 #include <tdeabc/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 TDEConfig * config, int aIdx ) 00129 { 00130 const char cIdx = char( int('A') + aIdx ); 00131 00132 static const TQString & field = TDEGlobal::staticQString( "field" ); 00133 static const TQString & func = TDEGlobal::staticQString( "func" ); 00134 static const TQString & contents = TDEGlobal::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 ( tqstricmp( 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( TDEConfig * config, int aIdx ) const { 00165 const char cIdx = char('A' + aIdx); 00166 static const TQString & field = TDEGlobal::staticQString( "field" ); 00167 static const TQString & func = TDEGlobal::staticQString( "func" ); 00168 static const TQString & contents = TDEGlobal::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 TDEABC::AddressBook *stdAb = TDEABC::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 TDEABC::AddressBook *stdAb = TDEABC::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 TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true ); 00481 00482 for( TQStringList::ConstIterator it = addressList.begin(); 00483 it != addressList.end(); ++it ) { 00484 TDEABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) ); 00485 00486 for ( TDEABC::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 TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true ); 00498 00499 for( TQStringList::ConstIterator it = addressList.begin(); 00500 it != addressList.end(); ++it ) { 00501 TDEABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) ); 00502 00503 for ( TDEABC::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 TDEConfig * 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 TDEConfig * 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 TDEConfig * 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( TDEConfig * 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 }