You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
945 lines
28 KiB
945 lines
28 KiB
// -*- mode: C++; c-file-style: "gnu" -*-
|
|
// kmsearchpattern.cpp
|
|
// Author: Marc Mutz <Marc@Mutz.com>
|
|
// This code is under GPL!
|
|
|
|
#include <config.h>
|
|
|
|
#include "kmaddrbook.h"
|
|
#include "kmsearchpattern.h"
|
|
#include "kmmsgdict.h"
|
|
#include "filterlog.h"
|
|
#include "kmkernel.h"
|
|
#include "kmmsgdict.h"
|
|
#include "kmfolder.h"
|
|
using KMail::FilterLog;
|
|
|
|
#include <libemailfunctions/email.h>
|
|
|
|
#include <kglobal.h>
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
#include <kconfig.h>
|
|
|
|
#include <kabc/stdaddressbook.h>
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <mimelib/string.h>
|
|
#include <mimelib/boyermor.h>
|
|
#include <mimelib/field.h>
|
|
#include <mimelib/headers.h>
|
|
|
|
#include <assert.h>
|
|
|
|
static const char* funcConfigNames[] =
|
|
{ "contains", "contains-not", "equals", "not-equal", "regexp",
|
|
"not-regexp", "greater", "less-or-equal", "less", "greater-or-equal",
|
|
"is-in-addressbook", "is-not-in-addressbook" , "is-in-category", "is-not-in-category",
|
|
"has-attachment", "has-no-attachment"};
|
|
static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames;
|
|
|
|
struct _statusNames {
|
|
const char* name;
|
|
KMMsgStatus status;
|
|
};
|
|
|
|
static struct _statusNames statusNames[] = {
|
|
{ "Important", KMMsgStatusFlag },
|
|
{ "New", KMMsgStatusNew },
|
|
{ "Unread", KMMsgStatusUnread | KMMsgStatusNew },
|
|
{ "Read", KMMsgStatusRead },
|
|
{ "Old", KMMsgStatusOld },
|
|
{ "Deleted", KMMsgStatusDeleted },
|
|
{ "Replied", KMMsgStatusReplied },
|
|
{ "Forwarded", KMMsgStatusForwarded },
|
|
{ "Queued", KMMsgStatusQueued },
|
|
{ "Sent", KMMsgStatusSent },
|
|
{ "Watched", KMMsgStatusWatched },
|
|
{ "Ignored", KMMsgStatusIgnored },
|
|
{ "To Do", KMMsgStatusTodo },
|
|
{ "Spam", KMMsgStatusSpam },
|
|
{ "Ham", KMMsgStatusHam },
|
|
{ "Has Attachment", KMMsgStatusHasAttach },
|
|
{ "Invitation", KMMsgStatusHasInvitation }
|
|
};
|
|
|
|
static const int numStatusNames = sizeof statusNames / sizeof ( struct _statusNames );
|
|
|
|
|
|
//==================================================
|
|
//
|
|
// class KMSearchRule (was: KMFilterRule)
|
|
//
|
|
//==================================================
|
|
|
|
KMSearchRule::KMSearchRule( const TQCString & field, Function func, const TQString & contents )
|
|
: mField( field ),
|
|
mFunction( func ),
|
|
mContents( contents )
|
|
{
|
|
}
|
|
|
|
KMSearchRule::KMSearchRule( const KMSearchRule & other )
|
|
: mField( other.mField ),
|
|
mFunction( other.mFunction ),
|
|
mContents( other.mContents )
|
|
{
|
|
}
|
|
|
|
const KMSearchRule & KMSearchRule::operator=( const KMSearchRule & other ) {
|
|
if ( this == &other )
|
|
return *this;
|
|
|
|
mField = other.mField;
|
|
mFunction = other.mFunction;
|
|
mContents = other.mContents;
|
|
|
|
return *this;
|
|
}
|
|
|
|
KMSearchRule * KMSearchRule::createInstance( const TQCString & field,
|
|
Function func,
|
|
const TQString & contents )
|
|
{
|
|
KMSearchRule *ret = 0;
|
|
if (field == "<status>")
|
|
ret = new KMSearchRuleStatus( field, func, contents );
|
|
else if ( field == "<age in days>" || field == "<size>" )
|
|
ret = new KMSearchRuleNumerical( field, func, contents );
|
|
else
|
|
ret = new KMSearchRuleString( field, func, contents );
|
|
|
|
return ret;
|
|
}
|
|
|
|
KMSearchRule * KMSearchRule::createInstance( const TQCString & field,
|
|
const char *func,
|
|
const TQString & contents )
|
|
{
|
|
return ( createInstance( field, configValueToFunc( func ), contents ) );
|
|
}
|
|
|
|
KMSearchRule * KMSearchRule::createInstance( const KMSearchRule & other )
|
|
{
|
|
return ( createInstance( other.field(), other.function(), other.contents() ) );
|
|
}
|
|
|
|
KMSearchRule * KMSearchRule::createInstanceFromConfig( const KConfig * config, int aIdx )
|
|
{
|
|
const char cIdx = char( int('A') + aIdx );
|
|
|
|
static const TQString & field = TDEGlobal::staticQString( "field" );
|
|
static const TQString & func = TDEGlobal::staticQString( "func" );
|
|
static const TQString & contents = TDEGlobal::staticQString( "contents" );
|
|
|
|
const TQCString &field2 = config->readEntry( field + cIdx ).latin1();
|
|
Function func2 = configValueToFunc( config->readEntry( func + cIdx ).latin1() );
|
|
const TQString & contents2 = config->readEntry( contents + cIdx );
|
|
|
|
if ( field2 == "<To or Cc>" ) // backwards compat
|
|
return KMSearchRule::createInstance( "<recipients>", func2, contents2 );
|
|
else
|
|
return KMSearchRule::createInstance( field2, func2, contents2 );
|
|
}
|
|
|
|
KMSearchRule::Function KMSearchRule::configValueToFunc( const char * str ) {
|
|
if ( !str )
|
|
return FuncNone;
|
|
|
|
for ( int i = 0 ; i < numFuncConfigNames ; ++i )
|
|
if ( tqstricmp( funcConfigNames[i], str ) == 0 ) return (Function)i;
|
|
|
|
return FuncNone;
|
|
}
|
|
|
|
TQString KMSearchRule::functionToString( Function function )
|
|
{
|
|
if ( function != FuncNone )
|
|
return funcConfigNames[int( function )];
|
|
else
|
|
return "invalid";
|
|
}
|
|
|
|
void KMSearchRule::writeConfig( KConfig * config, int aIdx ) const {
|
|
const char cIdx = char('A' + aIdx);
|
|
static const TQString & field = TDEGlobal::staticQString( "field" );
|
|
static const TQString & func = TDEGlobal::staticQString( "func" );
|
|
static const TQString & contents = TDEGlobal::staticQString( "contents" );
|
|
|
|
config->writeEntry( field + cIdx, TQString(mField) );
|
|
config->writeEntry( func + cIdx, functionToString( mFunction ) );
|
|
config->writeEntry( contents + cIdx, mContents );
|
|
}
|
|
|
|
bool KMSearchRule::matches( const DwString & aStr, KMMessage & msg,
|
|
const DwBoyerMoore *, int ) const
|
|
{
|
|
if ( !msg.isComplete() ) {
|
|
msg.fromDwString( aStr );
|
|
msg.setComplete( true );
|
|
}
|
|
return matches( &msg );
|
|
}
|
|
|
|
const TQString KMSearchRule::asString() const
|
|
{
|
|
TQString result = "\"" + mField + "\" <";
|
|
result += functionToString( mFunction );
|
|
result += "> \"" + mContents + "\"";
|
|
|
|
return result;
|
|
}
|
|
|
|
//==================================================
|
|
//
|
|
// class KMSearchRuleString
|
|
//
|
|
//==================================================
|
|
|
|
KMSearchRuleString::KMSearchRuleString( const TQCString & field,
|
|
Function func, const TQString & contents )
|
|
: KMSearchRule(field, func, contents)
|
|
{
|
|
if ( field.isEmpty() || field[0] == '<' )
|
|
mBmHeaderField = 0;
|
|
else // make sure you handle the unrealistic case of the message starting with mField
|
|
mBmHeaderField = new DwBoyerMoore(("\n" + field + ": ").data());
|
|
}
|
|
|
|
KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString & other )
|
|
: KMSearchRule( other ),
|
|
mBmHeaderField( 0 )
|
|
{
|
|
if ( other.mBmHeaderField )
|
|
mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
|
|
}
|
|
|
|
const KMSearchRuleString & KMSearchRuleString::operator=( const KMSearchRuleString & other )
|
|
{
|
|
if ( this == &other )
|
|
return *this;
|
|
|
|
setField( other.field() );
|
|
mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
|
|
setFunction( other.function() );
|
|
setContents( other.contents() );
|
|
delete mBmHeaderField; mBmHeaderField = 0;
|
|
if ( other.mBmHeaderField )
|
|
mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
|
|
|
|
return *this;
|
|
}
|
|
|
|
KMSearchRuleString::~KMSearchRuleString()
|
|
{
|
|
delete mBmHeaderField;
|
|
mBmHeaderField = 0;
|
|
}
|
|
|
|
bool KMSearchRuleString::isEmpty() const
|
|
{
|
|
return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
|
|
}
|
|
|
|
bool KMSearchRuleString::requiresBody() const
|
|
{
|
|
if (mBmHeaderField || (field() == "<recipients>" ))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg,
|
|
const DwBoyerMoore * aHeaderField, int aHeaderLen ) const
|
|
{
|
|
if ( isEmpty() )
|
|
return false;
|
|
|
|
bool rc = false;
|
|
|
|
const DwBoyerMoore * headerField = aHeaderField ? aHeaderField : mBmHeaderField ;
|
|
|
|
const int headerLen = ( aHeaderLen > -1 ? aHeaderLen : field().length() ) + 2 ; // +1 for ': '
|
|
|
|
if ( headerField ) {
|
|
static const DwBoyerMoore lflf( "\n\n" );
|
|
static const DwBoyerMoore lfcrlf( "\n\r\n" );
|
|
|
|
size_t endOfHeader = lflf.FindIn( aStr, 0 );
|
|
if ( endOfHeader == DwString::npos )
|
|
endOfHeader = lfcrlf.FindIn( aStr, 0 );
|
|
const DwString headers = ( endOfHeader == DwString::npos ) ? aStr : aStr.substr( 0, endOfHeader );
|
|
// In case the searched header is at the beginning, we have to prepend
|
|
// a newline - see the comment in KMSearchRuleString constructor
|
|
DwString fakedHeaders( "\n" );
|
|
size_t start = headerField->FindIn( fakedHeaders.append( headers ), 0, false );
|
|
// if the header field doesn't exist then return false for positive
|
|
// functions and true for negated functions (e.g. "does not
|
|
// contain"); note that all negated string functions correspond
|
|
// to an odd value
|
|
if ( start == DwString::npos )
|
|
rc = ( ( function() & 1 ) == 1 );
|
|
else {
|
|
start += headerLen;
|
|
size_t stop = aStr.find( '\n', start );
|
|
char ch = '\0';
|
|
while ( stop != DwString::npos && ( ( ch = aStr.at( stop + 1 ) ) == ' ' || ch == '\t' ) )
|
|
stop = aStr.find( '\n', stop + 1 );
|
|
const int len = stop == DwString::npos ? aStr.length() - start : stop - start ;
|
|
const TQCString codedValue( aStr.data() + start, len + 1 );
|
|
const TQString msgContents = KMMsgBase::decodeRFC2047String( codedValue ).stripWhiteSpace(); // FIXME: This needs to be changed for IDN support.
|
|
rc = matchesInternal( msgContents );
|
|
}
|
|
} else if ( field() == "<recipients>" ) {
|
|
static const DwBoyerMoore to("\nTo: ");
|
|
static const DwBoyerMoore cc("\nCc: ");
|
|
static const DwBoyerMoore bcc("\nBcc: ");
|
|
// <recipients> "contains" "foo" is true if any of the fields contains
|
|
// "foo", while <recipients> "does not contain" "foo" is true if none
|
|
// of the fields contains "foo"
|
|
if ( ( function() & 1 ) == 0 ) {
|
|
// positive function, e.g. "contains"
|
|
rc = ( matches( aStr, msg, &to, 2 ) ||
|
|
matches( aStr, msg, &cc, 2 ) ||
|
|
matches( aStr, msg, &bcc, 3 ) );
|
|
}
|
|
else {
|
|
// negated function, e.g. "does not contain"
|
|
rc = ( matches( aStr, msg, &to, 2 ) &&
|
|
matches( aStr, msg, &cc, 2 ) &&
|
|
matches( aStr, msg, &bcc, 3 ) );
|
|
}
|
|
}
|
|
if ( FilterLog::instance()->isLogging() ) {
|
|
TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
|
|
: "<font color=#FF0000>0 = </font>" );
|
|
msg += FilterLog::recode( asString() );
|
|
// only log headers bcause messages and bodies can be pretty large
|
|
// FIXME We have to separate the text which is used for filtering to be able to show it in the log
|
|
// if ( logContents )
|
|
// msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
|
|
FilterLog::instance()->add( msg, FilterLog::ruleResult );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool KMSearchRuleString::matches( const KMMessage * msg ) const
|
|
{
|
|
assert( msg );
|
|
|
|
if ( isEmpty() )
|
|
return false;
|
|
|
|
TQString msgContents;
|
|
// Show the value used to compare the rules against in the log.
|
|
// Overwrite the value for complete messages and all headers!
|
|
bool logContents = true;
|
|
|
|
if( field() == "<message>" ) {
|
|
|
|
// When searching in the complete message, we can't simply use msg->asString() here,
|
|
// as that wouldn't decode the body. Therefore we use the decoded body and all decoded
|
|
// header fields and add all to the one big search string.
|
|
msgContents += msg->bodyToUnicode();
|
|
const DwHeaders& headers = msg->headers();
|
|
const DwField * dwField = headers.FirstField();
|
|
while( dwField != 0 ) {
|
|
const char * const fieldName = dwField->FieldNameStr().c_str();
|
|
const TQString fieldValue = msg->headerFields( fieldName ).join( " " );
|
|
msgContents += " " + fieldValue;
|
|
dwField = dwField->Next();
|
|
}
|
|
logContents = false;
|
|
} else if ( field() == "<body>" ) {
|
|
msgContents = msg->bodyToUnicode();
|
|
logContents = false;
|
|
} else if ( field() == "<any header>" ) {
|
|
msgContents = msg->headerAsString();
|
|
logContents = false;
|
|
} else if ( field() == "<recipients>" ) {
|
|
// (mmutz 2001-11-05) hack to fix "<recipients> !contains foo" to
|
|
// meet user's expectations. See FAQ entry in KDE 2.2.2's KMail
|
|
// handbook
|
|
if ( function() == FuncEquals || function() == FuncNotEqual )
|
|
// do we need to treat this case specially? Ie.: What shall
|
|
// "equality" mean for recipients.
|
|
return matchesInternal( msg->headerField("To") )
|
|
|| matchesInternal( msg->headerField("Cc") )
|
|
|| matchesInternal( msg->headerField("Bcc") )
|
|
// sometimes messages have multiple Cc headers
|
|
|| matchesInternal( msg->cc() );
|
|
|
|
msgContents = msg->headerField("To");
|
|
if ( !msg->headerField("Cc").compare( msg->cc() ) )
|
|
msgContents += ", " + msg->headerField("Cc");
|
|
else
|
|
msgContents += ", " + msg->cc();
|
|
msgContents += ", " + msg->headerField("Bcc");
|
|
} else {
|
|
// make sure to treat messages with multiple header lines for
|
|
// the same header correctly
|
|
msgContents = msg->headerFields( field() ).join( " " );
|
|
}
|
|
|
|
if ( function() == FuncIsInAddressbook ||
|
|
function() == FuncIsNotInAddressbook ) {
|
|
// I think only the "from"-field makes sense.
|
|
msgContents = msg->headerField( field() );
|
|
if ( msgContents.isEmpty() )
|
|
return ( function() == FuncIsInAddressbook ) ? false : true;
|
|
}
|
|
|
|
// these two functions need the kmmessage therefore they don't call matchesInternal
|
|
if ( function() == FuncHasAttachment )
|
|
return ( msg->toMsgBase().attachmentState() == KMMsgHasAttachment );
|
|
if ( function() == FuncHasNoAttachment )
|
|
return ( ((KMMsgAttachmentState) msg->toMsgBase().attachmentState()) == KMMsgHasNoAttachment );
|
|
|
|
bool rc = matchesInternal( msgContents );
|
|
if ( FilterLog::instance()->isLogging() ) {
|
|
TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
|
|
: "<font color=#FF0000>0 = </font>" );
|
|
msg += FilterLog::recode( asString() );
|
|
// only log headers bcause messages and bodies can be pretty large
|
|
if ( logContents )
|
|
msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
|
|
FilterLog::instance()->add( msg, FilterLog::ruleResult );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
// helper, does the actual comparing
|
|
bool KMSearchRuleString::matchesInternal( const TQString & msgContents ) const
|
|
{
|
|
switch ( function() ) {
|
|
case KMSearchRule::FuncEquals:
|
|
return ( TQString::compare( msgContents.lower(), contents().lower() ) == 0 );
|
|
|
|
case KMSearchRule::FuncNotEqual:
|
|
return ( TQString::compare( msgContents.lower(), contents().lower() ) != 0 );
|
|
|
|
case KMSearchRule::FuncContains:
|
|
return ( msgContents.find( contents(), 0, false ) >= 0 );
|
|
|
|
case KMSearchRule::FuncContainsNot:
|
|
return ( msgContents.find( contents(), 0, false ) < 0 );
|
|
|
|
case KMSearchRule::FuncRegExp:
|
|
{
|
|
TQRegExp regexp( contents(), false );
|
|
return ( regexp.search( msgContents ) >= 0 );
|
|
}
|
|
|
|
case KMSearchRule::FuncNotRegExp:
|
|
{
|
|
TQRegExp regexp( contents(), false );
|
|
return ( regexp.search( msgContents ) < 0 );
|
|
}
|
|
|
|
case FuncIsGreater:
|
|
return ( TQString::compare( msgContents.lower(), contents().lower() ) > 0 );
|
|
|
|
case FuncIsLessOrEqual:
|
|
return ( TQString::compare( msgContents.lower(), contents().lower() ) <= 0 );
|
|
|
|
case FuncIsLess:
|
|
return ( TQString::compare( msgContents.lower(), contents().lower() ) < 0 );
|
|
|
|
case FuncIsGreaterOrEqual:
|
|
return ( TQString::compare( msgContents.lower(), contents().lower() ) >= 0 );
|
|
|
|
case FuncIsInAddressbook: {
|
|
KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
|
|
TQStringList addressList =
|
|
KPIM::splitEmailAddrList( msgContents.lower() );
|
|
for( TQStringList::ConstIterator it = addressList.begin();
|
|
( it != addressList.end() );
|
|
++it ) {
|
|
if ( !stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
case FuncIsNotInAddressbook: {
|
|
KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
|
|
TQStringList addressList =
|
|
KPIM::splitEmailAddrList( msgContents.lower() );
|
|
for( TQStringList::ConstIterator it = addressList.begin();
|
|
( it != addressList.end() );
|
|
++it ) {
|
|
if ( stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
case FuncIsInCategory: {
|
|
TQString category = contents();
|
|
TQStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() );
|
|
KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
|
|
|
|
for( TQStringList::ConstIterator it = addressList.begin();
|
|
it != addressList.end(); ++it ) {
|
|
KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
|
|
|
|
for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
|
|
if ( (*itAd).hasCategory(category) )
|
|
return true;
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
case FuncIsNotInCategory: {
|
|
TQString category = contents();
|
|
TQStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() );
|
|
KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
|
|
|
|
for( TQStringList::ConstIterator it = addressList.begin();
|
|
it != addressList.end(); ++it ) {
|
|
KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
|
|
|
|
for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
|
|
if ( (*itAd).hasCategory(category) )
|
|
return false;
|
|
|
|
}
|
|
return true;
|
|
}
|
|
default:
|
|
;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//==================================================
|
|
//
|
|
// class KMSearchRuleNumerical
|
|
//
|
|
//==================================================
|
|
|
|
KMSearchRuleNumerical::KMSearchRuleNumerical( const TQCString & field,
|
|
Function func, const TQString & contents )
|
|
: KMSearchRule(field, func, contents)
|
|
{
|
|
}
|
|
|
|
bool KMSearchRuleNumerical::isEmpty() const
|
|
{
|
|
bool ok = false;
|
|
contents().toInt( &ok );
|
|
|
|
return !ok;
|
|
}
|
|
|
|
|
|
bool KMSearchRuleNumerical::matches( const KMMessage * msg ) const
|
|
{
|
|
|
|
TQString msgContents;
|
|
int numericalMsgContents = 0;
|
|
int numericalValue = 0;
|
|
|
|
if ( field() == "<size>" ) {
|
|
numericalMsgContents = int( msg->msgLength() );
|
|
numericalValue = contents().toInt();
|
|
msgContents.setNum( numericalMsgContents );
|
|
} else if ( field() == "<age in days>" ) {
|
|
TQDateTime msgDateTime;
|
|
msgDateTime.setTime_t( msg->date() );
|
|
numericalMsgContents = msgDateTime.daysTo( TQDateTime::currentDateTime() );
|
|
numericalValue = contents().toInt();
|
|
msgContents.setNum( numericalMsgContents );
|
|
}
|
|
bool rc = matchesInternal( numericalValue, numericalMsgContents, msgContents );
|
|
if ( FilterLog::instance()->isLogging() ) {
|
|
TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
|
|
: "<font color=#FF0000>0 = </font>" );
|
|
msg += FilterLog::recode( asString() );
|
|
msg += " ( <i>" + TQString::number( numericalMsgContents ) + "</i> )";
|
|
FilterLog::instance()->add( msg, FilterLog::ruleResult );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool KMSearchRuleNumerical::matchesInternal( long numericalValue,
|
|
long numericalMsgContents, const TQString & msgContents ) const
|
|
{
|
|
switch ( function() ) {
|
|
case KMSearchRule::FuncEquals:
|
|
return ( numericalValue == numericalMsgContents );
|
|
|
|
case KMSearchRule::FuncNotEqual:
|
|
return ( numericalValue != numericalMsgContents );
|
|
|
|
case KMSearchRule::FuncContains:
|
|
return ( msgContents.find( contents(), 0, false ) >= 0 );
|
|
|
|
case KMSearchRule::FuncContainsNot:
|
|
return ( msgContents.find( contents(), 0, false ) < 0 );
|
|
|
|
case KMSearchRule::FuncRegExp:
|
|
{
|
|
TQRegExp regexp( contents(), false );
|
|
return ( regexp.search( msgContents ) >= 0 );
|
|
}
|
|
|
|
case KMSearchRule::FuncNotRegExp:
|
|
{
|
|
TQRegExp regexp( contents(), false );
|
|
return ( regexp.search( msgContents ) < 0 );
|
|
}
|
|
|
|
case FuncIsGreater:
|
|
return ( numericalMsgContents > numericalValue );
|
|
|
|
case FuncIsLessOrEqual:
|
|
return ( numericalMsgContents <= numericalValue );
|
|
|
|
case FuncIsLess:
|
|
return ( numericalMsgContents < numericalValue );
|
|
|
|
case FuncIsGreaterOrEqual:
|
|
return ( numericalMsgContents >= numericalValue );
|
|
|
|
case FuncIsInAddressbook: // since email-addresses are not numerical, I settle for false here
|
|
return false;
|
|
|
|
case FuncIsNotInAddressbook:
|
|
return false;
|
|
|
|
default:
|
|
;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
//==================================================
|
|
//
|
|
// class KMSearchRuleStatus
|
|
//
|
|
//==================================================
|
|
TQString englishNameForStatus( const KMMsgStatus& status )
|
|
{
|
|
for ( int i=0; i< numStatusNames; i++ ) {
|
|
if ( statusNames[i].status == status ) {
|
|
return statusNames[i].name;
|
|
}
|
|
}
|
|
return TQString();
|
|
}
|
|
|
|
KMSearchRuleStatus::KMSearchRuleStatus( const TQCString & field,
|
|
Function func, const TQString & aContents )
|
|
: KMSearchRule(field, func, aContents)
|
|
{
|
|
// the values are always in english, both from the conf file as well as
|
|
// the patternedit gui
|
|
mStatus = statusFromEnglishName( aContents );
|
|
}
|
|
|
|
KMSearchRuleStatus::KMSearchRuleStatus( int status, Function func )
|
|
: KMSearchRule( "<status>", func, englishNameForStatus( status ) )
|
|
{
|
|
mStatus = status;
|
|
}
|
|
|
|
KMMsgStatus KMSearchRuleStatus::statusFromEnglishName( const TQString & aStatusString )
|
|
{
|
|
for ( int i=0; i< numStatusNames; i++ ) {
|
|
if ( !aStatusString.compare( statusNames[i].name ) ) {
|
|
return statusNames[i].status;
|
|
}
|
|
}
|
|
return KMMsgStatusUnknown;
|
|
}
|
|
|
|
bool KMSearchRuleStatus::isEmpty() const
|
|
{
|
|
return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
|
|
}
|
|
|
|
bool KMSearchRuleStatus::matches( const DwString &, KMMessage &,
|
|
const DwBoyerMoore *, int ) const
|
|
{
|
|
assert( 0 );
|
|
return false; // don't warn
|
|
}
|
|
|
|
bool KMSearchRuleStatus::matches( const KMMessage * msg ) const
|
|
{
|
|
|
|
KMMsgStatus msgStatus = msg->status();
|
|
bool rc = false;
|
|
|
|
switch ( function() ) {
|
|
case FuncEquals: // fallthrough. So that "<status> 'is' 'read'" works
|
|
case FuncContains:
|
|
if (msgStatus & mStatus)
|
|
rc = true;
|
|
break;
|
|
case FuncNotEqual: // fallthrough. So that "<status> 'is not' 'read'" works
|
|
case FuncContainsNot:
|
|
if (! (msgStatus & mStatus) )
|
|
rc = true;
|
|
break;
|
|
// FIXME what about the remaining funcs, how can they make sense for
|
|
// stati?
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( FilterLog::instance()->isLogging() ) {
|
|
TQString msg = ( rc ? "<font color=#00FF00>1 = </font>"
|
|
: "<font color=#FF0000>0 = </font>" );
|
|
msg += FilterLog::recode( asString() );
|
|
FilterLog::instance()->add( msg, FilterLog::ruleResult );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
//==================================================
|
|
//
|
|
// class KMSearchPattern
|
|
//
|
|
//==================================================
|
|
|
|
KMSearchPattern::KMSearchPattern( const KConfig * config )
|
|
: TQPtrList<KMSearchRule>()
|
|
{
|
|
setAutoDelete( true );
|
|
if ( config )
|
|
readConfig( config );
|
|
else
|
|
init();
|
|
}
|
|
|
|
KMSearchPattern::~KMSearchPattern()
|
|
{
|
|
}
|
|
|
|
bool KMSearchPattern::matches( const KMMessage * msg, bool ignoreBody ) const
|
|
{
|
|
if ( isEmpty() )
|
|
return true;
|
|
|
|
TQPtrListIterator<KMSearchRule> it( *this );
|
|
switch ( mOperator ) {
|
|
case OpAnd: // all rules must match
|
|
for ( it.toFirst() ; it.current() ; ++it )
|
|
if ( !((*it)->requiresBody() && ignoreBody) )
|
|
if ( !(*it)->matches( msg ) )
|
|
return false;
|
|
return true;
|
|
case OpOr: // at least one rule must match
|
|
for ( it.toFirst() ; it.current() ; ++it )
|
|
if ( !((*it)->requiresBody() && ignoreBody) )
|
|
if ( (*it)->matches( msg ) )
|
|
return true;
|
|
// fall through
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool KMSearchPattern::matches( const DwString & aStr, bool ignoreBody ) const
|
|
{
|
|
if ( isEmpty() )
|
|
return true;
|
|
|
|
KMMessage msg;
|
|
TQPtrListIterator<KMSearchRule> it( *this );
|
|
switch ( mOperator ) {
|
|
case OpAnd: // all rules must match
|
|
for ( it.toFirst() ; it.current() ; ++it )
|
|
if ( !((*it)->requiresBody() && ignoreBody) )
|
|
if ( !(*it)->matches( aStr, msg ) )
|
|
return false;
|
|
return true;
|
|
case OpOr: // at least one rule must match
|
|
for ( it.toFirst() ; it.current() ; ++it )
|
|
if ( !((*it)->requiresBody() && ignoreBody) )
|
|
if ( (*it)->matches( aStr, msg ) )
|
|
return true;
|
|
// fall through
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool KMSearchPattern::matches( TQ_UINT32 serNum, bool ignoreBody ) const
|
|
{
|
|
if ( isEmpty() )
|
|
return true;
|
|
|
|
bool res;
|
|
int idx = -1;
|
|
KMFolder *folder = 0;
|
|
KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
|
|
if (!folder || (idx == -1) || (idx >= folder->count())) {
|
|
return false;
|
|
}
|
|
|
|
KMFolderOpener openFolder(folder, "searchptr");
|
|
KMMsgBase *msgBase = folder->getMsgBase(idx);
|
|
if (requiresBody() && !ignoreBody) {
|
|
bool unGet = !msgBase->isMessage();
|
|
KMMessage *msg = folder->getMsg(idx);
|
|
res = false;
|
|
if ( msg ) {
|
|
res = matches( msg, ignoreBody );
|
|
if (unGet)
|
|
folder->unGetMsg(idx);
|
|
}
|
|
} else {
|
|
res = matches( folder->getDwString(idx), ignoreBody );
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool KMSearchPattern::requiresBody() const {
|
|
TQPtrListIterator<KMSearchRule> it( *this );
|
|
for ( it.toFirst() ; it.current() ; ++it )
|
|
if ( (*it)->requiresBody() )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void KMSearchPattern::purify() {
|
|
TQPtrListIterator<KMSearchRule> it( *this );
|
|
it.toLast();
|
|
while ( it.current() )
|
|
if ( (*it)->isEmpty() ) {
|
|
#ifndef NDEBUG
|
|
kdDebug(5006) << "KMSearchPattern::purify(): removing " << (*it)->asString() << endl;
|
|
#endif
|
|
remove( *it );
|
|
} else {
|
|
--it;
|
|
}
|
|
}
|
|
|
|
void KMSearchPattern::readConfig( const KConfig * config ) {
|
|
init();
|
|
|
|
mName = config->readEntry("name");
|
|
if ( !config->hasKey("rules") ) {
|
|
kdDebug(5006) << "KMSearchPattern::readConfig: found legacy config! Converting." << endl;
|
|
importLegacyConfig( config );
|
|
return;
|
|
}
|
|
|
|
mOperator = config->readEntry("operator") == "or" ? OpOr : OpAnd;
|
|
|
|
const int nRules = config->readNumEntry( "rules", 0 );
|
|
|
|
for ( int i = 0 ; i < nRules ; i++ ) {
|
|
KMSearchRule * r = KMSearchRule::createInstanceFromConfig( config, i );
|
|
if ( r->isEmpty() )
|
|
delete r;
|
|
else
|
|
append( r );
|
|
}
|
|
}
|
|
|
|
void KMSearchPattern::importLegacyConfig( const KConfig * config ) {
|
|
KMSearchRule * rule = KMSearchRule::createInstance( config->readEntry("fieldA").latin1(),
|
|
config->readEntry("funcA").latin1(),
|
|
config->readEntry("contentsA") );
|
|
if ( rule->isEmpty() ) {
|
|
// if the first rule is invalid,
|
|
// we really can't do much heuristics...
|
|
delete rule;
|
|
return;
|
|
}
|
|
append( rule );
|
|
|
|
const TQString sOperator = config->readEntry("operator");
|
|
if ( sOperator == "ignore" ) return;
|
|
|
|
rule = KMSearchRule::createInstance( config->readEntry("fieldB").latin1(),
|
|
config->readEntry("funcB").latin1(),
|
|
config->readEntry("contentsB") );
|
|
if ( rule->isEmpty() ) {
|
|
delete rule;
|
|
return;
|
|
}
|
|
append( rule );
|
|
|
|
if ( sOperator == "or" ) {
|
|
mOperator = OpOr;
|
|
return;
|
|
}
|
|
// This is the interesting case...
|
|
if ( sOperator == "unless" ) { // meaning "and not", ie we need to...
|
|
// ...invert the function (e.g. "equals" <-> "doesn't equal")
|
|
// We simply toggle the last bit (xor with 0x1)... This assumes that
|
|
// KMSearchRule::Function's come in adjacent pairs of pros and cons
|
|
KMSearchRule::Function func = last()->function();
|
|
unsigned int intFunc = (unsigned int)func;
|
|
func = KMSearchRule::Function( intFunc ^ 0x1 );
|
|
|
|
last()->setFunction( func );
|
|
}
|
|
|
|
// treat any other case as "and" (our default).
|
|
}
|
|
|
|
void KMSearchPattern::writeConfig( KConfig * config ) const {
|
|
config->writeEntry("name", mName);
|
|
config->writeEntry("operator", (mOperator == KMSearchPattern::OpOr) ? "or" : "and" );
|
|
|
|
int i = 0;
|
|
for ( TQPtrListIterator<KMSearchRule> it( *this ) ; it.current() && i < FILTER_MAX_RULES ; ++i , ++it )
|
|
// we could do this ourselves, but we want the rules to be extensible,
|
|
// so we give the rule it's number and let it do the rest.
|
|
(*it)->writeConfig( config, i );
|
|
|
|
// save the total number of rules.
|
|
config->writeEntry( "rules", i );
|
|
}
|
|
|
|
void KMSearchPattern::init() {
|
|
clear();
|
|
mOperator = OpAnd;
|
|
mName = '<' + i18n("name used for a virgin filter","unknown") + '>';
|
|
}
|
|
|
|
TQString KMSearchPattern::asString() const {
|
|
TQString result;
|
|
if ( mOperator == OpOr )
|
|
result = i18n("(match any of the following)");
|
|
else
|
|
result = i18n("(match all of the following)");
|
|
|
|
for ( TQPtrListIterator<KMSearchRule> it( *this ) ; it.current() ; ++it )
|
|
result += "\n\t" + FilterLog::recode( (*it)->asString() );
|
|
|
|
return result;
|
|
}
|
|
|
|
const KMSearchPattern & KMSearchPattern::operator=( const KMSearchPattern & other ) {
|
|
if ( this == &other )
|
|
return *this;
|
|
|
|
setOp( other.op() );
|
|
setName( other.name() );
|
|
|
|
clear(); // ###
|
|
|
|
for ( TQPtrListIterator<KMSearchRule> it( other ) ; it.current() ; ++it ) {
|
|
KMSearchRule * rule = KMSearchRule::createInstance( **it ); // deep copy
|
|
append( rule );
|
|
}
|
|
|
|
return *this;
|
|
}
|