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.
 
 

778 lines
23 KiB

/*
* kmail: KDE mail client
* Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
* Copyright (C) 2012 Andras Mantia <amantia@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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.
*
*/
// my header
#include "mailfilter.h"
// other kmail headers
#include "filteractions/filteraction.h"
#include "filteractions/filteractiondict.h"
#include "filtermanager.h"
#include "filterlog.h"
#include "dialog/filteractionmissingaccountdialog.h"
using MailCommon::FilterLog;
#include "PimCommon/PimUtil"
// KDEPIMLIBS headers
#include <AgentManager>
// other KDE headers
#include <KLocalizedString>
#include <kmessagebox.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <krandom.h>
#include <algorithm>
#include <boost/bind.hpp>
using namespace MailCommon;
MailFilter::MailFilter()
{
generateRandomIdentifier();
bApplyOnInbound = true;
bApplyBeforeOutbound = false;
bApplyOnOutbound = false;
bApplyOnExplicit = true;
bStopProcessingHere = true;
bConfigureShortcut = false;
bConfigureToolbar = false;
bAutoNaming = true;
mApplicability = All;
bEnabled = true;
}
MailFilter::MailFilter(const KConfigGroup &aConfig, bool interactive, bool &needUpdate)
{
needUpdate = readConfig(aConfig, interactive);
}
MailFilter::MailFilter(const MailFilter &aFilter)
{
mIdentifier = aFilter.mIdentifier;
mPattern = aFilter.mPattern;
bApplyOnInbound = aFilter.applyOnInbound();
bApplyBeforeOutbound = aFilter.applyBeforeOutbound();
bApplyOnOutbound = aFilter.applyOnOutbound();
bApplyOnExplicit = aFilter.applyOnExplicit();
bStopProcessingHere = aFilter.stopProcessingHere();
bConfigureShortcut = aFilter.configureShortcut();
bConfigureToolbar = aFilter.configureToolbar();
mToolbarName = aFilter.toolbarName();
mApplicability = aFilter.applicability();
bAutoNaming = aFilter.isAutoNaming();
bEnabled = aFilter.isEnabled();
mIcon = aFilter.icon();
mShortcut = aFilter.shortcut();
QListIterator<FilterAction *> it(aFilter.mActions);
while (it.hasNext()) {
FilterAction *action = it.next();
FilterActionDesc *desc = FilterManager::filterActionDict()->value(action->name());
if (desc) {
FilterAction *f = desc->create();
if (f) {
f->argsFromString(action->argsAsString());
mActions.append(f);
}
}
}
mAccounts.clear();
QStringList::ConstIterator it2;
QStringList::ConstIterator end2 = aFilter.mAccounts.constEnd();
for (it2 = aFilter.mAccounts.constBegin(); it2 != end2; ++it2) {
mAccounts.append(*it2);
}
}
MailFilter::~MailFilter()
{
qDeleteAll(mActions);
}
int MailFilter::filterActionsMaximumSize()
{
return 8;
}
void MailFilter::generateRandomIdentifier()
{
mIdentifier = KRandom::randomString(16);
}
QString MailFilter::identifier() const
{
return mIdentifier;
}
QString MailFilter::name() const
{
return mPattern.name();
}
MailFilter::ReturnCode MailFilter::execActions(ItemContext &context, bool &stopIt, bool applyOnOutbound) const
{
ReturnCode status = NoResult;
QList<FilterAction *>::const_iterator it(mActions.constBegin());
QList<FilterAction *>::const_iterator end(mActions.constEnd());
for (; it != end; ++it) {
if (FilterLog::instance()->isLogging()) {
const QString logText(i18n("<b>Applying filter action:</b> %1",
(*it)->displayString()));
FilterLog::instance()->add(logText, FilterLog::AppliedAction);
}
FilterAction::ReturnCode result = (*it)->process(context, applyOnOutbound);
switch (result) {
case FilterAction::CriticalError:
if (FilterLog::instance()->isLogging()) {
const QString logText = QStringLiteral("<font color=#FF0000>%1</font>")
.arg(i18n("A critical error occurred. Processing stops here."));
FilterLog::instance()->add(logText, FilterLog::AppliedAction);
}
// in case it's a critical error: return immediately!
return CriticalError;
case FilterAction::ErrorButGoOn:
if (FilterLog::instance()->isLogging()) {
const QString logText = QStringLiteral("<font color=#FF0000>%1</font>")
.arg(i18n("A problem was found while applying this action."));
FilterLog::instance()->add(logText, FilterLog::AppliedAction);
}
default:
break;
}
}
if (status == NoResult) { // No filters matched, keep copy of message
status = GoOn;
}
stopIt = stopProcessingHere();
return status;
}
QList<FilterAction *> *MailFilter::actions()
{
return &mActions;
}
const QList<FilterAction *> *MailFilter::actions() const
{
return &mActions;
}
SearchPattern *MailFilter::pattern()
{
return &mPattern;
}
const SearchPattern *MailFilter::pattern() const
{
return &mPattern;
}
void MailFilter::setApplyOnOutbound(bool aApply)
{
bApplyOnOutbound = aApply;
}
void MailFilter::setApplyBeforeOutbound(bool aApply)
{
bApplyBeforeOutbound = aApply;
}
bool MailFilter::applyOnOutbound() const
{
return bApplyOnOutbound;
}
bool MailFilter::applyBeforeOutbound() const
{
return bApplyBeforeOutbound;
}
void MailFilter::setApplyOnInbound(bool aApply)
{
bApplyOnInbound = aApply;
}
bool MailFilter::applyOnInbound() const
{
return bApplyOnInbound;
}
void MailFilter::setApplyOnExplicit(bool aApply)
{
bApplyOnExplicit = aApply;
}
bool MailFilter::applyOnExplicit() const
{
return bApplyOnExplicit;
}
void MailFilter::setApplicability(AccountType aApply)
{
mApplicability = aApply;
}
MailFilter::AccountType MailFilter::applicability() const
{
return mApplicability;
}
SearchRule::RequiredPart MailFilter::requiredPart(const QString &id) const
{
//find the required message part needed for the filter
//this can be either only the Envelope, all Header or the CompleteMessage
//Makes the assumption that Envelope < Header < CompleteMessage
int requiredPart = SearchRule::Envelope;
if (!bEnabled || !applyOnAccount(id)) {
return static_cast<SearchRule::RequiredPart>(requiredPart);
}
if (pattern()) {
requiredPart = qMax(requiredPart, (int)pattern()->requiredPart()); // no pattern means always matches?
}
int requiredPartByActions = SearchRule::Envelope;
QList<FilterAction *> actionList = *actions();
if (!actionList.isEmpty()) {
requiredPartByActions = (*std::max_element(actionList.constBegin(), actionList.constEnd(),
boost::bind(&MailCommon::FilterAction::requiredPart, _1) <
boost::bind(&MailCommon::FilterAction::requiredPart, _2)))->requiredPart();
}
requiredPart = qMax(requiredPart, requiredPartByActions);
return static_cast<SearchRule::RequiredPart>(requiredPart);
}
void MailFilter::folderRemoved(const Akonadi::Collection &aFolder, const Akonadi::Collection &aNewFolder)
{
QListIterator<FilterAction *> it(mActions);
while (it.hasNext()) {
it.next()->folderRemoved(aFolder, aNewFolder);
}
}
void MailFilter::clearApplyOnAccount()
{
mAccounts.clear();
}
void MailFilter::setApplyOnAccount(const QString &id, bool aApply)
{
if (aApply && !mAccounts.contains(id)) {
mAccounts.append(id);
} else if (!aApply && mAccounts.contains(id)) {
mAccounts.removeAll(id);
}
}
bool MailFilter::applyOnAccount(const QString &id) const
{
if (applicability() == All) {
return true;
}
if (applicability() == ButImap) {
Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(id);
if (instance.isValid()) {
return !PimCommon::Util::isImapResource(instance.type().identifier());
} else {
return false;
}
}
if (applicability() == Checked) {
return mAccounts.contains(id);
}
return false;
}
void MailFilter::setStopProcessingHere(bool aStop)
{
bStopProcessingHere = aStop;
}
bool MailFilter::stopProcessingHere() const
{
return bStopProcessingHere;
}
void MailFilter::setConfigureShortcut(bool aShort)
{
bConfigureShortcut = aShort;
bConfigureToolbar = (bConfigureToolbar && bConfigureShortcut);
}
bool MailFilter::configureShortcut() const
{
return bConfigureShortcut;
}
void MailFilter::setConfigureToolbar(bool aTool)
{
bConfigureToolbar = (aTool && bConfigureShortcut);
}
bool MailFilter::configureToolbar() const
{
return bConfigureToolbar;
}
void MailFilter::setToolbarName(const QString &toolbarName)
{
mToolbarName = toolbarName;
}
void MailFilter::setShortcut(const QKeySequence &shortcut)
{
mShortcut = shortcut;
}
const QKeySequence &MailFilter::shortcut() const
{
return mShortcut;
}
void MailFilter::setIcon(const QString &icon)
{
mIcon = icon;
}
QString MailFilter::icon() const
{
return mIcon;
}
void MailFilter::setAutoNaming(bool useAutomaticNames)
{
bAutoNaming = useAutomaticNames;
}
bool MailFilter::isAutoNaming() const
{
return bAutoNaming;
}
//-----------------------------------------------------------------------------
bool MailFilter::readConfig(const KConfigGroup &config, bool interactive)
{
bool needUpdate = false;
// MKSearchPattern::readConfig ensures
// that the pattern is purified.
mPattern.readConfig(config);
mIdentifier = config.readEntry("identifier", KRandom::randomString(16));
const QStringList sets = config.readEntry("apply-on", QStringList());
if (sets.isEmpty() && !config.hasKey("apply-on")) {
bApplyBeforeOutbound = false;
bApplyOnOutbound = false;
bApplyOnInbound = true;
bApplyOnExplicit = true;
mApplicability = ButImap;
} else {
bApplyBeforeOutbound = bool(sets.contains(QStringLiteral("before-send-mail")));
bApplyOnInbound = bool(sets.contains(QStringLiteral("check-mail")));
bApplyOnOutbound = bool(sets.contains(QStringLiteral("send-mail")));
bApplyOnExplicit = bool(sets.contains(QStringLiteral("manual-filtering")));
mApplicability = (AccountType) config.readEntry(
"Applicability", (int)ButImap);
}
bStopProcessingHere = config.readEntry("StopProcessingHere", true);
bConfigureShortcut = config.readEntry("ConfigureShortcut", false);
QString shortcut(config.readEntry("Shortcut", QString()));
if (!shortcut.isEmpty()) {
QKeySequence sc(shortcut);
setShortcut(sc);
}
bConfigureToolbar = config.readEntry("ConfigureToolbar", false);
bConfigureToolbar = bConfigureToolbar && bConfigureShortcut;
mToolbarName = config.readEntry("ToolbarName", name());
mIcon = config.readEntry("Icon", "system-run");
bAutoNaming = config.readEntry("AutomaticName", false);
bEnabled = config.readEntry("Enabled", true);
QString actName, argsName;
mActions.clear();
int numActions = config.readEntry("actions", 0);
if (numActions > filterActionsMaximumSize()) {
numActions = filterActionsMaximumSize();
KMessageBox::information(0, i18n("<qt>Too many filter actions in filter rule <b>%1</b>.</qt>", mPattern.name()));
}
for (int i = 0; i < numActions; ++i) {
actName.sprintf("action-name-%d", i);
argsName.sprintf("action-args-%d", i);
// get the action description...
FilterActionDesc *desc = FilterManager::filterActionDict()->value(
config.readEntry(actName, QString()));
if (desc) {
//...create an instance...
FilterAction *fa = desc->create();
if (fa) {
//...load it with it's parameter...
if (interactive) {
const bool ret = fa->argsFromStringInteractive(config.readEntry(argsName, QString()), name());
if (ret) {
needUpdate = true;
}
} else {
fa->argsFromString(config.readEntry(argsName, QString()));
}
//...check if it's empty and...
if (!fa->isEmpty())
//...append it if it's not and...
{
mActions.append(fa);
} else
//...delete is else.
{
delete fa;
}
}
} else
KMessageBox::information(0 /* app-global modal dialog box */,
i18n("<qt>Unknown filter action <b>%1</b><br />in filter rule <b>%2</b>.<br />Ignoring it.</qt>",
config.readEntry(actName, QString()),
mPattern.name()));
}
mAccounts = config.readEntry("accounts-set", QStringList());
if (!mAccounts.isEmpty() && interactive) {
if (!MailCommon::FilterActionMissingAccountDialog::allAccountExist(mAccounts)) {
MailCommon::FilterActionMissingAccountDialog *dlg = new MailCommon::FilterActionMissingAccountDialog(mAccounts, name());
if (dlg->exec()) {
mAccounts = dlg->selectedAccount();
needUpdate = true;
}
delete dlg;
}
}
return needUpdate;
}
void MailFilter::generateSieveScript(QStringList &requires, QString &code)
{
mPattern.generateSieveScript(requires, code);
QList<FilterAction *>::const_iterator it;
QList<FilterAction *>::const_iterator end(mActions.constEnd());
code += QLatin1String(")\n{\n");
bool firstAction = true;
for (it = mActions.constBegin(); it != end; ++it) {
//Add endline here.
if (firstAction) {
firstAction = false;
} else {
code += QLatin1Char('\n');
}
code += QLatin1String(" ") + (*it)->sieveCode();
Q_FOREACH (const QString &str, (*it)->sieveRequires()) {
if (!requires.contains(str)) {
requires.append(str);
}
}
}
code += QLatin1String("\n}\n");
}
void MailFilter::writeConfig(KConfigGroup &config, bool exportFilter) const
{
mPattern.writeConfig(config);
config.writeEntry("identifier", mIdentifier);
QStringList sets;
if (bApplyOnInbound) {
sets.append(QStringLiteral("check-mail"));
}
if (bApplyBeforeOutbound) {
sets.append(QStringLiteral("before-send-mail"));
}
if (bApplyOnOutbound) {
sets.append(QStringLiteral("send-mail"));
}
if (bApplyOnExplicit) {
sets.append(QStringLiteral("manual-filtering"));
}
config.writeEntry("apply-on", sets);
config.writeEntry("StopProcessingHere", bStopProcessingHere);
config.writeEntry("ConfigureShortcut", bConfigureShortcut);
if (!mShortcut.isEmpty()) {
config.writeEntry("Shortcut", mShortcut.toString());
}
config.writeEntry("ConfigureToolbar", bConfigureToolbar);
config.writeEntry("ToolbarName", mToolbarName);
if (!mIcon.isEmpty()) {
config.writeEntry("Icon", mIcon);
}
config.writeEntry("AutomaticName", bAutoNaming);
config.writeEntry("Applicability", (int)mApplicability);
config.writeEntry("Enabled", bEnabled);
QString key;
int i;
QList<FilterAction *>::const_iterator it;
QList<FilterAction *>::const_iterator end(mActions.constEnd());
for (i = 0, it = mActions.constBegin(); it != end; ++it, ++i) {
config.writeEntry(key.sprintf("action-name-%d", i),
(*it)->name());
config.writeEntry(key.sprintf("action-args-%d", i),
exportFilter ? (*it)->argsAsStringReal() : (*it)->argsAsString());
}
config.writeEntry("actions", i);
if (!mAccounts.isEmpty()) {
config.writeEntry("accounts-set", mAccounts);
}
}
QString MailFilter::purify(bool removeAction)
{
QString informationAboutNotValidAction = mPattern.purify(removeAction);
QListIterator<FilterAction *> it(mActions);
it.toBack();
while (it.hasPrevious()) {
FilterAction *action = it.previous();
if (action->isEmpty()) {
if (!informationAboutNotValidAction.isEmpty()) {
informationAboutNotValidAction += QLatin1Char('\n');
}
informationAboutNotValidAction += action->informationAboutNotValidAction();
if (removeAction) {
mActions.removeAll(action);
}
}
}
if (!Akonadi::AgentManager::self()->instances().isEmpty()) { // safety test to ensure that Akonadi system is ready
// Remove invalid accounts from mAccounts - just to be tidy
QStringList::Iterator it2 = mAccounts.begin();
while (it2 != mAccounts.end()) {
if (!Akonadi::AgentManager::self()->instance(*it2).isValid()) {
it2 = mAccounts.erase(it2);
} else {
++it2;
}
}
}
return informationAboutNotValidAction;
}
bool MailFilter::isEmpty() const
{
return (mPattern.isEmpty() && mActions.isEmpty()) ||
((applicability() == Checked) && (bApplyOnInbound && mAccounts.isEmpty()));
}
QString MailFilter::toolbarName() const
{
if (mToolbarName.isEmpty()) {
return name();
} else {
return mToolbarName;
}
}
const QString MailFilter::asString() const
{
QString result;
result += "Filter name: " + name() + " (" + mIdentifier + ")\n";
result += mPattern.asString() + '\n';
result += QString("Filter is %1\n").arg(bEnabled ? QStringLiteral("enabled") : QStringLiteral("disabled"));
QList<FilterAction *>::const_iterator it(mActions.constBegin());
QList<FilterAction *>::const_iterator end(mActions.constEnd());
for (; it != end; ++it) {
result += " action: ";
result += (*it)->label();
result += ' ';
result += (*it)->argsAsString();
result += '\n';
}
result += "This filter belongs to the following sets:";
if (bApplyOnInbound) {
result += " Inbound";
}
if (bApplyBeforeOutbound) {
result += " before-Outbound";
}
if (bApplyOnOutbound) {
result += " Outbound";
}
if (bApplyOnExplicit) {
result += " Explicit";
}
result += '\n';
if (bApplyOnInbound && mApplicability == All) {
result += "This filter applies to all accounts.\n";
} else if (bApplyOnInbound && mApplicability == ButImap) {
result += "This filter applies to all but IMAP accounts.\n";
} else if (bApplyOnInbound) {
QStringList::ConstIterator it2;
result += "This filter applies to the following accounts:";
if (mAccounts.isEmpty()) {
result += " None";
} else {
for (it2 = mAccounts.begin(); it2 != mAccounts.end(); ++it2) {
if (Akonadi::AgentManager::self()->instance(*it2).isValid()) {
result += ' ' + Akonadi::AgentManager::self()->instance(*it2).name();
}
}
}
result += '\n';
}
if (bStopProcessingHere) {
result += "If it matches, processing stops at this filter.\n";
}
return result;
}
QDataStream &MailCommon::operator<<(QDataStream &stream, const MailCommon::MailFilter &filter)
{
stream << filter.mIdentifier;
stream << filter.mPattern.serialize();
stream << filter.mActions.count();
QListIterator<FilterAction *> it(filter.mActions);
while (it.hasNext()) {
const FilterAction *action = it.next();
stream << action->name();
stream << action->argsAsString();
}
stream << filter.mAccounts;
stream << filter.mIcon;
stream << filter.mToolbarName;
stream << filter.mShortcut;
stream << filter.bApplyOnInbound;
stream << filter.bApplyBeforeOutbound;
stream << filter.bApplyOnOutbound;
stream << filter.bApplyOnExplicit;
stream << filter.bStopProcessingHere;
stream << filter.bConfigureShortcut;
stream << filter.bConfigureToolbar;
stream << filter.bAutoNaming;
stream << filter.mApplicability;
stream << filter.bEnabled;
return stream;
}
QDataStream &MailCommon::operator>>(QDataStream &stream, MailCommon::MailFilter &filter)
{
QByteArray pattern;
int numberOfActions;
QKeySequence shortcut;
bool bApplyOnInbound;
bool bApplyBeforeOutbound;
bool bApplyOnOutbound;
bool bApplyOnExplicit;
bool bStopProcessingHere;
bool bConfigureShortcut;
bool bConfigureToolbar;
bool bAutoNaming;
int applicability;
bool bEnabled;
stream >> filter.mIdentifier;
stream >> pattern;
stream >> numberOfActions;
qDeleteAll(filter.mActions);
filter.mActions.clear();
for (int i = 0; i < numberOfActions; ++i) {
QString actionName;
QString actionArguments;
stream >> actionName;
stream >> actionArguments;
FilterActionDesc *description = FilterManager::filterActionDict()->value(actionName);
if (description) {
FilterAction *filterAction = description->create();
if (filterAction) {
filterAction->argsFromString(actionArguments);
filter.mActions.append(filterAction);
}
}
}
stream >> filter.mAccounts;
stream >> filter.mIcon;
stream >> filter.mToolbarName;
stream >> shortcut;
stream >> bApplyOnInbound;
stream >> bApplyBeforeOutbound;
stream >> bApplyOnOutbound;
stream >> bApplyOnExplicit;
stream >> bStopProcessingHere;
stream >> bConfigureShortcut;
stream >> bConfigureToolbar;
stream >> bAutoNaming;
stream >> applicability;
stream >> bEnabled;
filter.mPattern.deserialize(pattern);
filter.mShortcut = shortcut;
filter.bApplyOnInbound = bApplyOnInbound;
filter.bApplyBeforeOutbound = bApplyBeforeOutbound;
filter.bApplyOnOutbound = bApplyOnOutbound;
filter.bApplyOnExplicit = bApplyOnExplicit;
filter.bStopProcessingHere = bStopProcessingHere;
filter.bConfigureShortcut = bConfigureShortcut;
filter.bConfigureToolbar = bConfigureToolbar;
filter.bAutoNaming = bAutoNaming;
filter.bEnabled = bEnabled;
filter.mApplicability = static_cast<MailCommon::MailFilter::AccountType>(applicability);
return stream;
}
bool MailFilter::isEnabled() const
{
return bEnabled;
}
void MailFilter::setEnabled(bool enabled)
{
bEnabled = enabled;
}