diff options
Diffstat (limited to 'kmail/antispamwizard.cpp')
-rw-r--r-- | kmail/antispamwizard.cpp | 1151 |
1 files changed, 1151 insertions, 0 deletions
diff --git a/kmail/antispamwizard.cpp b/kmail/antispamwizard.cpp new file mode 100644 index 000000000..bb8d74bbb --- /dev/null +++ b/kmail/antispamwizard.cpp @@ -0,0 +1,1151 @@ +/* + This file is part of KMail. + Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de> + + KMail is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMail is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "antispamwizard.h" +#include "kcursorsaver.h" +#include "accountmanager.h" +#include "kmfilter.h" +#include "kmfilteraction.h" +#include "kmfiltermgr.h" +#include "kmkernel.h" +#include "kmfolderseldlg.h" +#include "kmfoldertree.h" +#include "kmmainwin.h" +#include "networkaccount.h" +#include "folderrequester.h" + +#include <kaction.h> +#include <kapplication.h> +#include <kdebug.h> +#include <kdialog.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kprocess.h> + +#include <qdom.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qtooltip.h> +#include <qwhatsthis.h> + +using namespace KMail; + +AntiSpamWizard::AntiSpamWizard( WizardMode mode, + QWidget* parent, KMFolderTree * mainFolderTree ) + : KWizard( parent ), + mInfoPage( 0 ), + mSpamRulesPage( 0 ), + mVirusRulesPage( 0 ), + mSummaryPage( 0 ), + mMode( mode ) +{ + // read the configuration for the anti-spam tools + ConfigReader reader( mMode, mToolList ); + reader.readAndMergeConfig(); + mToolList = reader.getToolList(); + +#ifndef NDEBUG + if ( mMode == AntiSpam ) + kdDebug(5006) << endl << "Considered anti-spam tools: " << endl; + else + kdDebug(5006) << endl << "Considered anti-virus tools: " << endl; +#endif + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { +#ifndef NDEBUG + kdDebug(5006) << "Predefined tool: " << (*it).getId() << endl; + kdDebug(5006) << "Config version: " << (*it).getVersion() << endl; + kdDebug(5006) << "Selection priority: " << (*it).getPrio() << endl; + kdDebug(5006) << "Displayed name: " << (*it).getVisibleName() << endl; + kdDebug(5006) << "Executable: " << (*it).getExecutable() << endl; + kdDebug(5006) << "WhatsThis URL: " << (*it).getWhatsThisText() << endl; + kdDebug(5006) << "Filter name: " << (*it).getFilterName() << endl; + kdDebug(5006) << "Detection command: " << (*it).getDetectCmd() << endl; + kdDebug(5006) << "Learn spam command: " << (*it).getSpamCmd() << endl; + kdDebug(5006) << "Learn ham command: " << (*it).getHamCmd() << endl; + kdDebug(5006) << "Detection header: " << (*it).getDetectionHeader() << endl; + kdDebug(5006) << "Detection pattern: " << (*it).getDetectionPattern() << endl; + kdDebug(5006) << "Use as RegExp: " << (*it).isUseRegExp() << endl; + kdDebug(5006) << "Supports Bayes Filter: " << (*it).useBayesFilter() << endl; + kdDebug(5006) << "Type: " << (*it).getType() << endl << endl; +#endif + } + + setCaption( ( mMode == AntiSpam ) ? i18n( "Anti-Spam Wizard" ) + : i18n( "Anti-Virus Wizard" ) ); + mInfoPage = new ASWizInfoPage( mMode, 0, "" ); + addPage( mInfoPage, + ( mMode == AntiSpam ) + ? i18n( "Welcome to the KMail Anti-Spam Wizard" ) + : i18n( "Welcome to the KMail Anti-Virus Wizard" ) ); + connect( mInfoPage, SIGNAL( selectionChanged( void ) ), + this, SLOT( checkProgramsSelections( void ) ) ); + + if ( mMode == AntiSpam ) { + mSpamRulesPage = new ASWizSpamRulesPage( 0, "", mainFolderTree ); + addPage( mSpamRulesPage, i18n( "Options to fine-tune the handling of spam messages" )); + connect( mSpamRulesPage, SIGNAL( selectionChanged( void ) ), + this, SLOT( slotBuildSummary( void ) ) ); + } + else { + mVirusRulesPage = new ASWizVirusRulesPage( 0, "", mainFolderTree ); + addPage( mVirusRulesPage, i18n( "Options to fine-tune the handling of virus messages" )); + connect( mVirusRulesPage, SIGNAL( selectionChanged( void ) ), + this, SLOT( checkVirusRulesSelections( void ) ) ); + } + + connect( this, SIGNAL( helpClicked( void) ), + this, SLOT( slotHelpClicked( void ) ) ); + + setNextEnabled( mInfoPage, false ); + + if ( mMode == AntiSpam ) { + mSummaryPage = new ASWizSummaryPage( 0, "" ); + addPage( mSummaryPage, i18n( "Summary of changes to be made by this wizard" ) ); + setNextEnabled( mSpamRulesPage, true ); + setFinishEnabled( mSummaryPage, true ); + } + + QTimer::singleShot( 0, this, SLOT( checkToolAvailability( void ) ) ); +} + + +void AntiSpamWizard::accept() +{ + if ( mSpamRulesPage ) { + kdDebug( 5006 ) << "Folder name for messages classified as spam is " + << mSpamRulesPage->selectedSpamFolderName() << endl; + kdDebug( 5006 ) << "Folder name for messages classified as unsure is " + << mSpamRulesPage->selectedUnsureFolderName() << endl; + } + if ( mVirusRulesPage ) + kdDebug( 5006 ) << "Folder name for viruses is " + << mVirusRulesPage->selectedFolderName() << endl; + + KMFilterActionDict dict; + QValueList<KMFilter*> filterList; + bool replaceExistingFilters = false; + + // Let's start with virus detection and handling, + // so we can avoid spam checks for viral messages + if ( mMode == AntiVirus ) { + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) && + ( mVirusRulesPage->pipeRulesSelected() && (*it).isVirusTool() ) ) + { + // pipe messages through the anti-virus tools, + // one single filter for each tool + // (could get combined but so it's easier to understand for the user) + KMFilter* pipeFilter = new KMFilter(); + QPtrList<KMFilterAction>* pipeFilterActions = pipeFilter->actions(); + KMFilterAction* pipeFilterAction = dict["filter app"]->create(); + pipeFilterAction->argsFromString( (*it).getDetectCmd() ); + pipeFilterActions->append( pipeFilterAction ); + KMSearchPattern* pipeFilterPattern = pipeFilter->pattern(); + pipeFilterPattern->setName( uniqueNameFor( (*it).getFilterName() ) ); + pipeFilterPattern->append( KMSearchRule::createInstance( "<size>", + KMSearchRule::FuncIsGreaterOrEqual, "0" ) ); + pipeFilter->setApplyOnOutbound( false); + pipeFilter->setApplyOnInbound(); + pipeFilter->setApplyOnExplicit(); + pipeFilter->setStopProcessingHere( false ); + pipeFilter->setConfigureShortcut( false ); + + filterList.append( pipeFilter ); + } + } + + if ( mVirusRulesPage->moveRulesSelected() ) + { + // Sort out viruses depending on header fields set by the tools + KMFilter* virusFilter = new KMFilter(); + QPtrList<KMFilterAction>* virusFilterActions = virusFilter->actions(); + KMFilterAction* virusFilterAction1 = dict["transfer"]->create(); + virusFilterAction1->argsFromString( mVirusRulesPage->selectedFolderName() ); + virusFilterActions->append( virusFilterAction1 ); + if ( mVirusRulesPage->markReadRulesSelected() ) { + KMFilterAction* virusFilterAction2 = dict["set status"]->create(); + virusFilterAction2->argsFromString( "R" ); // Read + virusFilterActions->append( virusFilterAction2 ); + } + KMSearchPattern* virusFilterPattern = virusFilter->pattern(); + virusFilterPattern->setName( uniqueNameFor( i18n( "Virus handling" ) ) ); + virusFilterPattern->setOp( KMSearchPattern::OpOr ); + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() )) + { + if ( (*it).isVirusTool() ) + { + const QCString header = (*it).getDetectionHeader().ascii(); + const QString & pattern = (*it).getDetectionPattern(); + if ( (*it).isUseRegExp() ) + virusFilterPattern->append( + KMSearchRule::createInstance( header, + KMSearchRule::FuncRegExp, pattern ) ); + else + virusFilterPattern->append( + KMSearchRule::createInstance( header, + KMSearchRule::FuncContains, pattern ) ); + } + } + } + virusFilter->setApplyOnOutbound( false); + virusFilter->setApplyOnInbound(); + virusFilter->setApplyOnExplicit(); + virusFilter->setStopProcessingHere( true ); + virusFilter->setConfigureShortcut( false ); + + filterList.append( virusFilter ); + } + } + else { // AntiSpam mode + // TODO Existing filters with same name are replaced. This is hardcoded + // ATM and needs to be replaced with a value from a (still missing) + // checkbox in the GUI. At least, the replacement is announced in the GUI. + replaceExistingFilters = true; + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) && + (*it).isSpamTool() && !(*it).isDetectionOnly() ) + { + // pipe messages through the anti-spam tools, + // one single filter for each tool + // (could get combined but so it's easier to understand for the user) + KMFilter* pipeFilter = new KMFilter(); + QPtrList<KMFilterAction>* pipeFilterActions = pipeFilter->actions(); + KMFilterAction* pipeFilterAction = dict["filter app"]->create(); + pipeFilterAction->argsFromString( (*it).getDetectCmd() ); + pipeFilterActions->append( pipeFilterAction ); + KMSearchPattern* pipeFilterPattern = pipeFilter->pattern(); + if ( replaceExistingFilters ) + pipeFilterPattern->setName( (*it).getFilterName() ); + else + pipeFilterPattern->setName( uniqueNameFor( (*it).getFilterName() ) ); + pipeFilterPattern->append( KMSearchRule::createInstance( "<size>", + KMSearchRule::FuncIsLessOrEqual, "256000" ) ); + pipeFilter->setApplyOnOutbound( false); + pipeFilter->setApplyOnInbound(); + pipeFilter->setApplyOnExplicit(); + pipeFilter->setStopProcessingHere( false ); + pipeFilter->setConfigureShortcut( false ); + + filterList.append( pipeFilter ); + } + } + + // Sort out spam depending on header fields set by the tools + KMFilter* spamFilter = new KMFilter(); + QPtrList<KMFilterAction>* spamFilterActions = spamFilter->actions(); + if ( mSpamRulesPage->moveSpamSelected() ) + { + KMFilterAction* spamFilterAction1 = dict["transfer"]->create(); + spamFilterAction1->argsFromString( mSpamRulesPage->selectedSpamFolderName() ); + spamFilterActions->append( spamFilterAction1 ); + } + KMFilterAction* spamFilterAction2 = dict["set status"]->create(); + spamFilterAction2->argsFromString( "P" ); // Spam + spamFilterActions->append( spamFilterAction2 ); + if ( mSpamRulesPage->markAsReadSelected() ) { + KMFilterAction* spamFilterAction3 = dict["set status"]->create(); + spamFilterAction3->argsFromString( "R" ); // Read + spamFilterActions->append( spamFilterAction3 ); + } + KMSearchPattern* spamFilterPattern = spamFilter->pattern(); + if ( replaceExistingFilters ) + spamFilterPattern->setName( i18n( "Spam handling" ) ); + else + spamFilterPattern->setName( uniqueNameFor( i18n( "Spam handling" ) ) ); + spamFilterPattern->setOp( KMSearchPattern::OpOr ); + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) ) + { + if ( (*it).isSpamTool() ) + { + const QCString header = (*it).getDetectionHeader().ascii(); + const QString & pattern = (*it).getDetectionPattern(); + if ( (*it).isUseRegExp() ) + spamFilterPattern->append( + KMSearchRule::createInstance( header, + KMSearchRule::FuncRegExp, pattern ) ); + else + spamFilterPattern->append( + KMSearchRule::createInstance( header, + KMSearchRule::FuncContains, pattern ) ); + } + } + } + spamFilter->setApplyOnOutbound( false); + spamFilter->setApplyOnInbound(); + spamFilter->setApplyOnExplicit(); + spamFilter->setStopProcessingHere( true ); + spamFilter->setConfigureShortcut( false ); + filterList.append( spamFilter ); + + if ( mSpamRulesPage->moveUnsureSelected() ) + { + // Sort out messages classified as unsure + bool atLeastOneUnsurePattern = false; + KMFilter* unsureFilter = new KMFilter(); + QPtrList<KMFilterAction>* unsureFilterActions = unsureFilter->actions(); + KMFilterAction* unsureFilterAction1 = dict["transfer"]->create(); + unsureFilterAction1->argsFromString( mSpamRulesPage->selectedUnsureFolderName() ); + unsureFilterActions->append( unsureFilterAction1 ); + KMSearchPattern* unsureFilterPattern = unsureFilter->pattern(); + if ( replaceExistingFilters ) + unsureFilterPattern->setName( i18n( "Semi spam (unsure) handling" ) ); + else + unsureFilterPattern->setName( uniqueNameFor( i18n( "Semi spam (unsure) handling" ) ) ); + unsureFilterPattern->setOp( KMSearchPattern::OpOr ); + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) ) + { + if ( (*it).isSpamTool() && (*it).hasTristateDetection()) + { + atLeastOneUnsurePattern = true; + const QCString header = (*it).getDetectionHeader().ascii(); + const QString & pattern = (*it).getDetectionPattern2(); + if ( (*it).isUseRegExp() ) + unsureFilterPattern->append( + KMSearchRule::createInstance( header, + KMSearchRule::FuncRegExp, pattern ) ); + else + unsureFilterPattern->append( + KMSearchRule::createInstance( header, + KMSearchRule::FuncContains, pattern ) ); + } + } + } + unsureFilter->setApplyOnOutbound( false); + unsureFilter->setApplyOnInbound(); + unsureFilter->setApplyOnExplicit(); + unsureFilter->setStopProcessingHere( true ); + unsureFilter->setConfigureShortcut( false ); + + if ( atLeastOneUnsurePattern ) + filterList.append( unsureFilter ); + else + delete unsureFilter; + } + + // Classify messages manually as Spam + KMFilter* classSpamFilter = new KMFilter(); + classSpamFilter->setIcon( "mail_spam" ); + QPtrList<KMFilterAction>* classSpamFilterActions = classSpamFilter->actions(); + KMFilterAction* classSpamFilterActionFirst = dict["set status"]->create(); + classSpamFilterActionFirst->argsFromString( "P" ); + classSpamFilterActions->append( classSpamFilterActionFirst ); + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) + && (*it).useBayesFilter() && !(*it).isDetectionOnly() ) + { + KMFilterAction* classSpamFilterAction = dict["execute"]->create(); + classSpamFilterAction->argsFromString( (*it).getSpamCmd() ); + classSpamFilterActions->append( classSpamFilterAction ); + } + } + if ( mSpamRulesPage->moveSpamSelected() ) + { + KMFilterAction* classSpamFilterActionLast = dict["transfer"]->create(); + classSpamFilterActionLast->argsFromString( mSpamRulesPage->selectedSpamFolderName() ); + classSpamFilterActions->append( classSpamFilterActionLast ); + } + + KMSearchPattern* classSpamFilterPattern = classSpamFilter->pattern(); + if ( replaceExistingFilters ) + classSpamFilterPattern->setName( i18n( "Classify as spam" ) ); + else + classSpamFilterPattern->setName( uniqueNameFor( i18n( "Classify as spam" ) ) ); + classSpamFilterPattern->append( KMSearchRule::createInstance( "<size>", + KMSearchRule::FuncIsGreaterOrEqual, "0" ) ); + classSpamFilter->setApplyOnOutbound( false); + classSpamFilter->setApplyOnInbound( false ); + classSpamFilter->setApplyOnExplicit( false ); + classSpamFilter->setStopProcessingHere( true ); + classSpamFilter->setConfigureShortcut( true ); + classSpamFilter->setConfigureToolbar( true ); + filterList.append( classSpamFilter ); + + // Classify messages manually as not Spam / as Ham + KMFilter* classHamFilter = new KMFilter(); + classHamFilter->setIcon( "mail_ham" ); + QPtrList<KMFilterAction>* classHamFilterActions = classHamFilter->actions(); + KMFilterAction* classHamFilterActionFirst = dict["set status"]->create(); + classHamFilterActionFirst->argsFromString( "H" ); + classHamFilterActions->append( classHamFilterActionFirst ); + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) + && (*it).useBayesFilter() && !(*it).isDetectionOnly() ) + { + KMFilterAction* classHamFilterAction = dict["execute"]->create(); + classHamFilterAction->argsFromString( (*it).getHamCmd() ); + classHamFilterActions->append( classHamFilterAction ); + } + } + KMSearchPattern* classHamFilterPattern = classHamFilter->pattern(); + if ( replaceExistingFilters ) + classHamFilterPattern->setName( i18n( "Classify as NOT spam" ) ); + else + classHamFilterPattern->setName( uniqueNameFor( i18n( "Classify as NOT spam" ) ) ); + classHamFilterPattern->append( KMSearchRule::createInstance( "<size>", + KMSearchRule::FuncIsGreaterOrEqual, "0" ) ); + classHamFilter->setApplyOnOutbound( false); + classHamFilter->setApplyOnInbound( false ); + classHamFilter->setApplyOnExplicit( false ); + classHamFilter->setStopProcessingHere( true ); + classHamFilter->setConfigureShortcut( true ); + classHamFilter->setConfigureToolbar( true ); + filterList.append( classHamFilter ); + } + + /* Now that all the filters have been added to the list, tell + * the filter manager about it. That will emit filterListUpdate + * which will result in the filter list in kmmainwidget being + * initialized. This should happend only once. */ + if ( !filterList.isEmpty() ) + KMKernel::self()->filterMgr()->appendFilters( + filterList, replaceExistingFilters ); + + QDialog::accept(); +} + + +void AntiSpamWizard::checkProgramsSelections() +{ + bool status = false; + bool supportUnsure = false; + + mSpamToolsUsed = false; + mVirusToolsUsed = false; + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) ) + { + status = true; + if ( (*it).isSpamTool() ) { + mSpamToolsUsed = true; + if ( (*it).hasTristateDetection() ) + supportUnsure = true; + } + if ( (*it).isVirusTool() ) + mVirusToolsUsed = true; + } + } + + if ( mMode == AntiSpam ) { + mSpamRulesPage->allowUnsureFolderSelection( supportUnsure ); + slotBuildSummary(); + } + + if ( ( mMode == AntiVirus ) && mVirusToolsUsed ) + checkVirusRulesSelections(); + + setNextEnabled( mInfoPage, status ); +} + + +void AntiSpamWizard::checkVirusRulesSelections() +{ + setFinishEnabled( mVirusRulesPage, anyVirusOptionChecked() ); +} + + +void AntiSpamWizard::checkToolAvailability() +{ + // this can take some time to find the tools + KCursorSaver busy( KBusyPtr::busy() ); + + bool found = false; + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + QString text( i18n("Scanning for %1...").arg( (*it).getId() ) ); + mInfoPage->setScanProgressText( text ); + if ( (*it).isSpamTool() && (*it).isServerBased() ) { + // check the configured account for pattern in <server> + QString pattern = (*it).getServerPattern(); + kdDebug(5006) << "Testing for server pattern:" << pattern << endl; + + AccountManager* mgr = kmkernel->acctMgr(); + KMAccount* account = mgr->first(); + while ( account ) { + if ( account->type() == "pop" || account->type().contains( "imap" ) ) { + const NetworkAccount * n = dynamic_cast<const NetworkAccount*>( account ); + if ( n && n->host().lower().contains( pattern.lower() ) ) { + mInfoPage->addAvailableTool( (*it).getVisibleName() ); + found = true; + } + } + account = mgr->next(); + } + } + else { + // check the availability of the application + KApplication::kApplication()->processEvents( 200 ); + if ( !checkForProgram( (*it).getExecutable() ) ) { + mInfoPage->addAvailableTool( (*it).getVisibleName() ); + found = true; + } + } + } + if ( found ) + mInfoPage->setScanProgressText( ( mMode == AntiSpam ) + ? i18n("Scanning for anti-spam tools finished.") + : i18n("Scanning for anti-virus tools finished.") ); + else + mInfoPage->setScanProgressText( ( mMode == AntiSpam ) + ? i18n("<p>No spam detection tools have been found. " + "Install your spam detection software and " + "re-run this wizard.</p>") + : i18n("Scanning complete. No anti-virus tools found.") ); +} + + +void AntiSpamWizard::slotHelpClicked() +{ + if ( mMode == AntiSpam ) + kapp->invokeHelp( "the-anti-spam-wizard", "kmail" ); + else + kapp->invokeHelp( "the-anti-virus-wizard", "kmail" ); +} + + +void AntiSpamWizard::slotBuildSummary() +{ + QString text; + QString newFilters; + QString replaceFilters; + + if ( mMode == AntiVirus ) { + text = ""; // TODO add summary for the virus part + } + else { // AntiSpam mode + if ( mSpamRulesPage->markAsReadSelected() ) + text = i18n( "<p>Messages classified as spam are marked as read." ); + else + text = i18n( "<p>Messages classified as spam are not marked as read." ); + + if ( mSpamRulesPage->moveSpamSelected() ) + text += i18n( "<br>Spam messages are moved into the folder named <i>" ) + + mSpamRulesPage->selectedSpamFolderName() + "</i>.</p>"; + else + text += i18n( "<br>Spam messages are not moved into a certain folder.</p>" ); + + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) && + (*it).isSpamTool() && !(*it).isDetectionOnly() ) { + sortFilterOnExistance( (*it).getFilterName(), newFilters, replaceFilters ); + } + } + sortFilterOnExistance( i18n( "Spam handling" ), newFilters, replaceFilters ); + + // The need for a andling of status "probably spam" depends on the tools chosen + if ( mSpamRulesPage->moveUnsureSelected() ) { + bool atLeastOneUnsurePattern = false; + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) ) { + if ( (*it).isSpamTool() && (*it).hasTristateDetection()) + atLeastOneUnsurePattern = true; + } + } + if ( atLeastOneUnsurePattern ) { + sortFilterOnExistance( i18n( "Semi spam (unsure) handling" ), + newFilters, replaceFilters ); + text += i18n( "<p>The folder for messages classified as unsure (probably spam) is <i>" ) + + mSpamRulesPage->selectedUnsureFolderName() + "</i>.</p>"; + } + } + + // Manual classification via toolbar icon / manually applied filter action + sortFilterOnExistance( i18n( "Classify as spam" ), + newFilters, replaceFilters ); + sortFilterOnExistance( i18n( "Classify as NOT spam" ), + newFilters, replaceFilters ); + + // Show the filters in the summary + if ( !newFilters.isEmpty() ) + text += i18n( "<p>The wizard will create the following filters:<ul>" ) + + newFilters + "</ul></p>"; + if ( !replaceFilters.isEmpty() ) + text += i18n( "<p>The wizard will replace the following filters:<ul>" ) + + replaceFilters + "</ul></p>"; + } + + mSummaryPage->setSummaryText( text ); +} + + +int AntiSpamWizard::checkForProgram( const QString &executable ) +{ + kdDebug(5006) << "Testing for executable:" << executable << endl; + KProcess process; + process << executable; + process.setUseShell( true ); + process.start( KProcess::Block ); + return process.exitStatus(); +} + + +bool AntiSpamWizard::anyVirusOptionChecked() +{ + return ( mVirusRulesPage->moveRulesSelected() + || mVirusRulesPage->pipeRulesSelected() ); +} + + +const QString AntiSpamWizard::uniqueNameFor( const QString & name ) +{ + return KMKernel::self()->filterMgr()->createUniqueName( name ); +} + + +void AntiSpamWizard::sortFilterOnExistance( + const QString & intendedFilterName, + QString & newFilters, QString & replaceFilters ) +{ + if ( uniqueNameFor( intendedFilterName ) == intendedFilterName ) + newFilters += "<li>" + intendedFilterName + "</li>"; + else + replaceFilters += "<li>" + intendedFilterName + "</li>"; +} + + +//--------------------------------------------------------------------------- +AntiSpamWizard::SpamToolConfig::SpamToolConfig( QString toolId, + int configVersion, int prio, QString name, QString exec, + QString url, QString filter, QString detection, QString spam, QString ham, + QString header, QString pattern, QString pattern2, QString serverPattern, + bool detectionOnly, bool regExp, bool bayesFilter, bool tristateDetection, + WizardMode type ) + : mId( toolId ), mVersion( configVersion ), mPrio( prio ), + mVisibleName( name ), mExecutable( exec ), mWhatsThisText( url ), + mFilterName( filter ), mDetectCmd( detection ), mSpamCmd( spam ), + mHamCmd( ham ), mDetectionHeader( header ), mDetectionPattern( pattern ), + mDetectionPattern2( pattern2 ), mServerPattern( serverPattern ), + mDetectionOnly( detectionOnly ), + mUseRegExp( regExp ), mSupportsBayesFilter( bayesFilter ), + mSupportsUnsure( tristateDetection ), mType( type ) +{ +} + + +bool AntiSpamWizard::SpamToolConfig::isServerBased() const +{ + return !mServerPattern.isEmpty(); +} + + +//--------------------------------------------------------------------------- +AntiSpamWizard::ConfigReader::ConfigReader( WizardMode mode, + QValueList<SpamToolConfig> & configList ) + : mToolList( configList ), + mMode( mode ) +{ + if ( mMode == AntiSpam ) + mConfig = new KConfig( "kmail.antispamrc", true ); + else + mConfig = new KConfig( "kmail.antivirusrc", true ); +} + +AntiSpamWizard::ConfigReader::~ConfigReader( ) +{ + delete mConfig; +} + + +void AntiSpamWizard::ConfigReader::readAndMergeConfig() +{ + QString groupName = ( mMode == AntiSpam ) + ? QString("Spamtool #%1") + : QString("Virustool #%1"); + // read the configuration from the global config file + mConfig->setReadDefaults( true ); + KConfigGroup general( mConfig, "General" ); + int registeredTools = general.readNumEntry( "tools", 0 ); + for (int i = 1; i <= registeredTools; i++) + { + KConfigGroup toolConfig( mConfig, groupName.arg( i ) ); + if( !toolConfig.readBoolEntry( "HeadersOnly", false ) ) + mToolList.append( readToolConfig( toolConfig ) ); + } + + // read the configuration from the user config file + // and merge newer config data + mConfig->setReadDefaults( false ); + KConfigGroup user_general( mConfig, "General" ); + int user_registeredTools = user_general.readNumEntry( "tools", 0 ); + for (int i = 1; i <= user_registeredTools; i++) + { + KConfigGroup toolConfig( mConfig, groupName.arg( i ) ); + if( !toolConfig.readBoolEntry( "HeadersOnly", false ) ) + mergeToolConfig( readToolConfig( toolConfig ) ); + } + // Make sure to have add least one tool listed even when the + // config file was not found or whatever went wrong + // Currently only works for spam tools + if ( mMode == AntiSpam ) { + if ( registeredTools < 1 && user_registeredTools < 1 ) + mToolList.append( createDummyConfig() ); + sortToolList(); + } +} + + +AntiSpamWizard::SpamToolConfig + AntiSpamWizard::ConfigReader::readToolConfig( KConfigGroup & configGroup ) +{ + QString id = configGroup.readEntry( "Ident" ); + int version = configGroup.readNumEntry( "Version" ); +#ifndef NDEBUG + kdDebug(5006) << "Found predefined tool: " << id << endl; + kdDebug(5006) << "With config version : " << version << endl; +#endif + int prio = configGroup.readNumEntry( "Priority", 1 ); + QString name = configGroup.readEntry( "VisibleName" ); + QString executable = configGroup.readEntry( "Executable" ); + QString url = configGroup.readEntry( "URL" ); + QString filterName = configGroup.readEntry( "PipeFilterName" ); + QString detectCmd = configGroup.readEntry( "PipeCmdDetect" ); + QString spamCmd = configGroup.readEntry( "ExecCmdSpam" ); + QString hamCmd = configGroup.readEntry( "ExecCmdHam" ); + QString header = configGroup.readEntry( "DetectionHeader" ); + QString pattern = configGroup.readEntry( "DetectionPattern" ); + QString pattern2 = configGroup.readEntry( "DetectionPattern2" ); + QString serverPattern = configGroup.readEntry( "ServerPattern" ); + bool detectionOnly = configGroup.readBoolEntry( "DetectionOnly", false ); + bool useRegExp = configGroup.readBoolEntry( "UseRegExp" ); + bool supportsBayes = configGroup.readBoolEntry( "SupportsBayes", false ); + bool supportsUnsure = configGroup.readBoolEntry( "SupportsUnsure", false ); + return SpamToolConfig( id, version, prio, name, executable, url, + filterName, detectCmd, spamCmd, hamCmd, + header, pattern, pattern2, serverPattern, + detectionOnly, useRegExp, + supportsBayes, supportsUnsure, mMode ); +} + + +AntiSpamWizard::SpamToolConfig AntiSpamWizard::ConfigReader::createDummyConfig() +{ + return SpamToolConfig( "spamassassin", 0, 1, + "SpamAssassin", "spamassassin -V", + "http://spamassassin.org", "SpamAssassin Check", + "spamassassin -L", + "sa-learn -L --spam --no-rebuild --single", + "sa-learn -L --ham --no-rebuild --single", + "X-Spam-Flag", "yes", "", "", + false, false, true, false, AntiSpam ); +} + + +void AntiSpamWizard::ConfigReader::mergeToolConfig( AntiSpamWizard::SpamToolConfig config ) +{ + bool found = false; + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { +#ifndef NDEBUG + kdDebug(5006) << "Check against tool: " << (*it).getId() << endl; + kdDebug(5006) << "Against version : " << (*it).getVersion() << endl; +#endif + if ( (*it).getId() == config.getId() ) + { + found = true; + if ( (*it).getVersion() < config.getVersion() ) + { +#ifndef NDEBUG + kdDebug(5006) << "Replacing config ..." << endl; +#endif + mToolList.remove( it ); + mToolList.append( config ); + } + break; + } + } + if ( !found ) + mToolList.append( config ); +} + + +void AntiSpamWizard::ConfigReader::sortToolList() +{ + QValueList<SpamToolConfig> tmpList; + SpamToolConfig config; + + while ( !mToolList.isEmpty() ) { + QValueListIterator<SpamToolConfig> highest; + int priority = 0; // ascending + for ( QValueListIterator<SpamToolConfig> it = mToolList.begin(); + it != mToolList.end(); ++it ) { + if ( (*it).getPrio() > priority ) { + priority = (*it).getPrio(); + highest = it; + } + } + config = (*highest); + tmpList.append( config ); + mToolList.remove( highest ); + } + for ( QValueListIterator<SpamToolConfig> it = tmpList.begin(); + it != tmpList.end(); ++it ) { + mToolList.append( (*it) ); + } +} + + +//--------------------------------------------------------------------------- +ASWizPage::ASWizPage( QWidget * parent, const char * name, + const QString *bannerName ) + : QWidget( parent, name ) +{ + QString banner = "kmwizard.png"; + if ( bannerName && !bannerName->isEmpty() ) + banner = *bannerName; + + mLayout = new QHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() ); + mPixmap = new QPixmap( UserIcon(banner) ); + mBannerLabel = new QLabel( this ); + mBannerLabel->setPixmap( *mPixmap ); + mBannerLabel->setScaledContents( false ); + mBannerLabel->setFrameShape( QFrame::StyledPanel ); + mBannerLabel->setFrameShadow( QFrame::Sunken ); + + mLayout->addWidget( mBannerLabel ); + mLayout->addItem( new QSpacerItem( 5, 5, QSizePolicy::Minimum, QSizePolicy::Expanding ) ); +} + + +//--------------------------------------------------------------------------- +ASWizInfoPage::ASWizInfoPage( AntiSpamWizard::WizardMode mode, + QWidget * parent, const char * name ) + : ASWizPage( parent, name ) +{ + QBoxLayout * layout = new QVBoxLayout( mLayout ); + + mIntroText = new QLabel( this ); + mIntroText->setText( + ( mode == AntiSpamWizard::AntiSpam ) + ? i18n( + "The wizard will search for any tools to do spam detection\n" + "and setup KMail to work with them." + ) + : i18n( + "<p>Here you can get some assistance in setting up KMail's filter " + "rules to use some commonly-known anti-virus tools.</p>" + "<p>The wizard can detect those tools on your computer as " + "well as create filter rules to classify messages using these " + "tools and to separate messages containing viruses. " + "The wizard will not take any existing filter " + "rules into consideration: it will always append the new rules.</p>" + "<p><b>Warning:</b> As KMail appears to be frozen during the scan of the " + "messages for viruses, you may encounter problems with " + "the responsiveness of KMail because anti-virus tool " + "operations are usually time consuming; please consider " + "deleting the filter rules created by the wizard to get " + "back to the former behavior." + ) ); + layout->addWidget( mIntroText ); + + mScanProgressText = new QLabel( this ); + mScanProgressText->setText( "" ) ; + layout->addWidget( mScanProgressText ); + + mToolsList = new KListBox( this ); + mToolsList->hide(); + mToolsList->setSelectionMode( QListBox::Multi ); + mToolsList->setRowMode( QListBox::FixedNumber ); + mToolsList->setRowMode( 10 ); + layout->addWidget( mToolsList ); + connect( mToolsList, SIGNAL(selectionChanged()), + this, SLOT(processSelectionChange(void)) ); + + mSelectionHint = new QLabel( this ); + mSelectionHint->setText( "" ); + layout->addWidget( mSelectionHint ); + + layout->addStretch(); +} + + +void ASWizInfoPage::setScanProgressText( const QString &toolName ) +{ + mScanProgressText->setText( toolName ); +} + + +void ASWizInfoPage::addAvailableTool( const QString &visibleName ) +{ + QString listName = visibleName; + mToolsList->insertItem( listName ); + if ( !mToolsList->isVisible() ) + { + mToolsList->show(); + mToolsList->setSelected( 0, true ); + mSelectionHint->setText( i18n("<p>Please select the tools to be used " + "for the detection and go " + "to the next page.</p>") ); + } +} + +bool ASWizInfoPage::isProgramSelected( const QString &visibleName ) +{ + QString listName = visibleName; + return mToolsList->isSelected( mToolsList->findItem( listName ) ); +} + + +void ASWizInfoPage::processSelectionChange() +{ + emit selectionChanged(); +} + + +//--------------------------------------------------------------------------- +ASWizSpamRulesPage::ASWizSpamRulesPage( QWidget * parent, const char * name, + KMFolderTree * mainFolderTree ) + : ASWizPage( parent, name ) +{ + QVBoxLayout *layout = new QVBoxLayout( mLayout ); + + mMarkRules = new QCheckBox( i18n("&Mark detected spam messages as read"), this ); + QWhatsThis::add( mMarkRules, + i18n( "Mark messages which have been classified as spam as read.") ); + layout->addWidget( mMarkRules); + + mMoveSpamRules = new QCheckBox( i18n("Move &known spam to:"), this ); + QWhatsThis::add( mMoveSpamRules, + i18n( "The default folder for spam messages is the trash folder, " + "but you may change that in the folder view below.") ); + layout->addWidget( mMoveSpamRules ); + + mFolderReqForSpamFolder = new FolderRequester( this, mainFolderTree ); + mFolderReqForSpamFolder->setFolder( "trash" ); + mFolderReqForSpamFolder->setMustBeReadWrite( true ); + mFolderReqForSpamFolder->setShowOutbox( false ); + mFolderReqForSpamFolder->setShowImapFolders( false ); + + QHBoxLayout *hLayout1 = new QHBoxLayout( layout ); + hLayout1->addSpacing( KDialog::spacingHint() * 3 ); + hLayout1->addWidget( mFolderReqForSpamFolder ); + + mMoveUnsureRules = new QCheckBox( i18n("Move &probable spam to:"), this ); + QWhatsThis::add( mMoveUnsureRules, + i18n( "The default folder is the inbox folder, but you may change that " + "in the folder view below.<p>" + "Not all tools support a classification as unsure. If you haven't " + "selected a capable tool, you can't select a folder as well.") ); + layout->addWidget( mMoveUnsureRules ); + + mFolderReqForUnsureFolder = new FolderRequester( this, mainFolderTree ); + mFolderReqForUnsureFolder->setFolder( "inbox" ); + mFolderReqForUnsureFolder->setMustBeReadWrite( true ); + mFolderReqForUnsureFolder->setShowOutbox( false ); + mFolderReqForUnsureFolder->setShowImapFolders( false ); + + QHBoxLayout *hLayout2 = new QHBoxLayout( layout ); + hLayout2->addSpacing( KDialog::spacingHint() * 3 ); + hLayout2->addWidget( mFolderReqForUnsureFolder ); + + layout->addStretch(); + + connect( mMarkRules, SIGNAL(clicked()), + this, SLOT(processSelectionChange(void)) ); + connect( mMoveSpamRules, SIGNAL(clicked()), + this, SLOT(processSelectionChange(void)) ); + connect( mMoveUnsureRules, SIGNAL(clicked()), + this, SLOT(processSelectionChange(void)) ); + connect( mFolderReqForSpamFolder, SIGNAL(folderChanged(KMFolder*)), + this, SLOT(processSelectionChange(KMFolder*)) ); + connect( mFolderReqForUnsureFolder, SIGNAL(folderChanged(KMFolder*)), + this, SLOT(processSelectionChange(KMFolder*)) ); + + mMarkRules->setChecked( true ); + mMoveSpamRules->setChecked( true ); +} + + +bool ASWizSpamRulesPage::markAsReadSelected() const +{ + return mMarkRules->isChecked(); +} + + +bool ASWizSpamRulesPage::moveSpamSelected() const +{ + return mMoveSpamRules->isChecked(); +} + + +bool ASWizSpamRulesPage::moveUnsureSelected() const +{ + return mMoveUnsureRules->isChecked(); +} + + +QString ASWizSpamRulesPage::selectedSpamFolderName() const +{ + QString name = "trash"; + if ( mFolderReqForSpamFolder->folder() ) + name = mFolderReqForSpamFolder->folder()->idString(); + return name; +} + + +QString ASWizSpamRulesPage::selectedUnsureFolderName() const +{ + QString name = "inbox"; + if ( mFolderReqForUnsureFolder->folder() ) + name = mFolderReqForUnsureFolder->folder()->idString(); + return name; +} + + +void ASWizSpamRulesPage::processSelectionChange() +{ + mFolderReqForSpamFolder->setEnabled( mMoveSpamRules->isChecked() ); + mFolderReqForUnsureFolder->setEnabled( mMoveUnsureRules->isChecked() ); + emit selectionChanged(); +} + + +void ASWizSpamRulesPage::processSelectionChange( KMFolder* ) +{ + processSelectionChange(); +} + + +void ASWizSpamRulesPage::allowUnsureFolderSelection( bool enabled ) +{ + mMoveUnsureRules->setEnabled( enabled ); + mMoveUnsureRules->setShown( enabled ); + mFolderReqForUnsureFolder->setEnabled( enabled ); + mFolderReqForUnsureFolder->setShown( enabled ); +} + + +//--------------------------------------------------------------------------- +ASWizVirusRulesPage::ASWizVirusRulesPage( QWidget * parent, const char * name, + KMFolderTree * mainFolderTree ) + : ASWizPage( parent, name ) +{ + QGridLayout *grid = new QGridLayout( mLayout, 5, 1, KDialog::spacingHint() ); + + mPipeRules = new QCheckBox( i18n("Check messages using the anti-virus tools"), this ); + QWhatsThis::add( mPipeRules, + i18n( "Let the anti-virus tools check your messages. The wizard " + "will create appropriate filters. The messages are usually " + "marked by the tools so that following filters can react " + "on this and, for example, move virus messages to a special folder.") ); + grid->addWidget( mPipeRules, 0, 0 ); + + mMoveRules = new QCheckBox( i18n("Move detected viral messages to the selected folder"), this ); + QWhatsThis::add( mMoveRules, + i18n( "A filter to detect messages classified as virus-infected and to move " + "those messages into a predefined folder is created. The " + "default folder is the trash folder, but you may change that " + "in the folder view.") ); + grid->addWidget( mMoveRules, 1, 0 ); + + mMarkRules = new QCheckBox( i18n("Additionally, mark detected viral messages as read"), this ); + mMarkRules->setEnabled( false ); + QWhatsThis::add( mMarkRules, + i18n( "Mark messages which have been classified as " + "virus-infected as read, as well as moving them " + "to the selected folder.") ); + grid->addWidget( mMarkRules, 2, 0 ); + + QString s = "trash"; + mFolderTree = new SimpleFolderTree( this, mainFolderTree, s, true ); + grid->addWidget( mFolderTree, 3, 0 ); + + connect( mPipeRules, SIGNAL(clicked()), + this, SLOT(processSelectionChange(void)) ); + connect( mMoveRules, SIGNAL(clicked()), + this, SLOT(processSelectionChange(void)) ); + connect( mMarkRules, SIGNAL(clicked()), + this, SLOT(processSelectionChange(void)) ); + connect( mMoveRules, SIGNAL( toggled( bool ) ), + mMarkRules, SLOT( setEnabled( bool ) ) ); +} + +bool ASWizVirusRulesPage::pipeRulesSelected() const +{ + return mPipeRules->isChecked(); +} + + +bool ASWizVirusRulesPage::moveRulesSelected() const +{ + return mMoveRules->isChecked(); +} + +bool ASWizVirusRulesPage::markReadRulesSelected() const +{ + return mMarkRules->isChecked(); +} + + +QString ASWizVirusRulesPage::selectedFolderName() const +{ + QString name = "trash"; + if ( mFolderTree->folder() ) + name = mFolderTree->folder()->idString(); + return name; +} + +void ASWizVirusRulesPage::processSelectionChange() +{ + emit selectionChanged(); +} + + +//--------------------------------------------------------------------------- +ASWizSummaryPage::ASWizSummaryPage( QWidget * parent, const char * name ) + : ASWizPage( parent, name ) +{ + QBoxLayout * layout = new QVBoxLayout( mLayout ); + + mSummaryText = new QLabel( this ); + layout->addWidget( mSummaryText ); + layout->addStretch(); +} + + +void ASWizSummaryPage::setSummaryText( const QString & text ) +{ + mSummaryText->setText( text ); +} + + +#include "antispamwizard.moc" |