diff --git a/libksieve/ksieve/scriptbuilder.h b/libksieve/ksieve/scriptbuilder.h index 9d95b36387..7b08ebff3a 100644 --- a/libksieve/ksieve/scriptbuilder.h +++ b/libksieve/ksieve/scriptbuilder.h @@ -1,80 +1,80 @@ /* -*- c++ -*- ksieve/interfaces/scriptbuilder.h This file is part of KSieve, the KDE internet mail/usenet news message filtering library. Copyright (c) 2002-2003 Marc Mutz KSieve 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. KSieve 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. */ #ifndef KSIEVE_KSIEVE_SCRIPTBUILDER_H #define KSIEVE_KSIEVE_SCRIPTBUILDER_H class QString; namespace KSieve { class Error; class ScriptBuilder { public: virtual ~ScriptBuilder() {} virtual void taggedArgument( const QString & tag ) = 0; virtual void stringArgument( const QString & string, bool multiLine, const QString & embeddedHashComment ) = 0; virtual void numberArgument( unsigned long number, char quantifier ) = 0; virtual void stringListArgumentStart() = 0; virtual void stringListEntry( const QString & string, bool multiLine, const QString & embeddedHashComment ) = 0; virtual void stringListArgumentEnd() = 0; - virtual void commandStart( const QString & identifier ) = 0; - virtual void commandEnd() = 0; + virtual void commandStart( const QString & identifier, int lineNumber ) = 0; + virtual void commandEnd(int lineNumber) = 0; virtual void testStart( const QString & identifier ) = 0; virtual void testEnd() = 0; virtual void testListStart() = 0; virtual void testListEnd() = 0; - virtual void blockStart() = 0; - virtual void blockEnd() = 0; + virtual void blockStart(int lineNumber) = 0; + virtual void blockEnd(int lineNumber) = 0; /** A hash comment always includes an implicit lineFeed() at it's end. */ virtual void hashComment( const QString & comment ) = 0; /** Bracket comments inclde explicit lineFeed()s in their content */ virtual void bracketComment( const QString & comment ) = 0; virtual void lineFeed() = 0; virtual void error( const Error & error ) = 0; virtual void finished() = 0; }; } // namespace KSieve #endif diff --git a/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.cpp b/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.cpp index 61edb858fd..9a08a8453e 100644 --- a/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.cpp +++ b/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.cpp @@ -1,135 +1,146 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "generateglobalscriptjob.h" #include "libksieve/kmanagesieve/sievejob.h" #include using namespace KSieveUi; GenerateGlobalScriptJob::GenerateGlobalScriptJob(const KUrl &url, QObject *parent) : QObject(parent), mCurrentUrl(url), - mMasterjob(0), + mMasterJob(0), mUserJob(0) { } GenerateGlobalScriptJob::~GenerateGlobalScriptJob() { - if (mMasterjob) - mMasterjob->kill(); - if (mUserJob) + kill(); +} + +void GenerateGlobalScriptJob::kill() +{ + if (mMasterJob) { + mMasterJob->kill(); + } + mMasterJob = 0; + + if (mUserJob) { mUserJob->kill(); + } + mUserJob = 0; } + void GenerateGlobalScriptJob::addUserActiveScripts(const QStringList &lstScript) { mListUserActiveScripts = lstScript; } void GenerateGlobalScriptJob::start() { if (mCurrentUrl.isEmpty()) { Q_EMIT error(i18n("Path is not specified.")); return; } writeUserScript(); } void GenerateGlobalScriptJob::writeMasterScript() { const QString masterScript = QLatin1String("# MASTER\n" "#\n" "# This file is authoritative for your system and MUST BE KEPT ACTIVE.\n" "#\n" "# Altering it is likely to render your account dysfunctional and may\n" "# be violating your organizational or corporate policies.\n" "# \n" "# For more information on the mechanism and the conventions behind\n" "# this script, see http://wiki.kolab.org/KEP:14\n" "#\n" "\n" "require [\"include\"];\n" "\n" "# OPTIONAL: Includes for all or a group of users\n" "# include :global \"all-users\";\n" "# include :global \"this-group-of-users\";\n" "\n" "# The script maintained by the general management system\n" "include :personal :optional \"MANAGEMENT\";\n" "\n" "# The script(s) maintained by one or more editors available to the user\n" "include :personal :optional \"USER\";\n"); KUrl url(mCurrentUrl); url.setFileName(QLatin1String("MASTER")); - mMasterjob = KManageSieve::SieveJob::put(url, masterScript, true, true ); - connect( mMasterjob, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), + mMasterJob = KManageSieve::SieveJob::put(url, masterScript, true, true ); + connect( mMasterJob, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), this, SLOT(slotPutMasterResult(KManageSieve::SieveJob*,bool)) ); } void GenerateGlobalScriptJob::slotPutMasterResult( KManageSieve::SieveJob *, bool success ) { if (!success) { Q_EMIT error(i18n("Error when we wrote \"MASTER\" script on server.")); return; } - mMasterjob = 0; + mMasterJob = 0; writeUserScript(); } void GenerateGlobalScriptJob::writeUserScript() { QString userScript = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "require [\"include\"];\n"); Q_FOREACH (const QString &activeScript, mListUserActiveScripts) { userScript += QString::fromLatin1("\ninclude :personal \"%1\";").arg(activeScript); } KUrl url(mCurrentUrl); url.setFileName(QLatin1String("USER")); mUserJob = KManageSieve::SieveJob::put(url, userScript, false, false ); connect( mUserJob, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), this, SLOT(slotPutUserResult(KManageSieve::SieveJob*,bool)) ); } void GenerateGlobalScriptJob::slotPutUserResult( KManageSieve::SieveJob *, bool success ) { mUserJob = 0; if (!success) { Q_EMIT error(i18n("Error when we wrote \"User\" script on server.")); return; } disableAllOtherScripts(); } void GenerateGlobalScriptJob::disableAllOtherScripts() { //TODO Q_EMIT success(); } diff --git a/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.h b/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.h index c09471e29f..6dc8c1b0e4 100644 --- a/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.h +++ b/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.h @@ -1,58 +1,59 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #ifndef GENERATEGLOBALSCRIPTJOB_H #define GENERATEGLOBALSCRIPTJOB_H #include #include #include namespace KManageSieve { class SieveJob; } namespace KSieveUi { class GenerateGlobalScriptJob : public QObject { Q_OBJECT public: explicit GenerateGlobalScriptJob(const KUrl &url, QObject *parent=0); ~GenerateGlobalScriptJob(); void start(); + void kill(); void addUserActiveScripts(const QStringList &lstScript); Q_SIGNALS: void success(); void error(const QString &msgError); private Q_SLOTS: void slotPutMasterResult( KManageSieve::SieveJob *, bool success ); void slotPutUserResult( KManageSieve::SieveJob *, bool success ); private: void disableAllOtherScripts(); void writeMasterScript(); void writeUserScript(); QStringList mListUserActiveScripts; KUrl mCurrentUrl; - KManageSieve::SieveJob *mMasterjob; + KManageSieve::SieveJob *mMasterJob; KManageSieve::SieveJob *mUserJob; }; } #endif // GENERATEGLOBALSCRIPTJOB_H diff --git a/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.cpp b/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.cpp index da664e5821..02de7a4c08 100644 --- a/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.cpp +++ b/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.cpp @@ -1,154 +1,159 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "parseuserscriptjob.h" #include "ksieveui/scriptsparsing/parsingutil.h" #include #include using namespace KSieveUi; ParseUserScriptJob::ParseUserScriptJob(const KUrl &url, QObject *parent) : QObject(parent), mSieveJob(0) , mCurrentUrl(url) { } ParseUserScriptJob::~ParseUserScriptJob() { - if ( mSieveJob ) - mSieveJob->kill(); - mSieveJob = 0; + kill(); } KUrl ParseUserScriptJob::scriptUrl() const { return mCurrentUrl; } void ParseUserScriptJob::start() { if (mCurrentUrl.isEmpty()) { emitError(i18n("Path is not specified.")); return; } if ( mSieveJob ) mSieveJob->kill(); mActiveScripts = QStringList(); mError = QString(); mSieveJob = KManageSieve::SieveJob::get( mCurrentUrl ); connect( mSieveJob, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), this, SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool)) ); } void ParseUserScriptJob::slotGetResult( KManageSieve::SieveJob *, bool, const QString & script, bool ) { mSieveJob = 0; if (script.isEmpty()) { emitError(i18n("Script is empty.")); return; } bool result; const QStringList lst = parsescript(script, result); if (result) emitSuccess(lst); else emitError(i18n("Script parsing error")); } void ParseUserScriptJob::emitError(const QString &msgError) { mError = msgError; emit finished(this); } void ParseUserScriptJob::emitSuccess(const QStringList &activeScriptList) { mActiveScripts = activeScriptList; emit finished(this); } - QStringList ParseUserScriptJob::parsescript(const QString &script, bool &result) { QStringList lst; const QDomDocument doc = ParsingUtil::parseScript(script, result); if (result) { lst = extractActiveScript(doc); } return lst; } QStringList ParseUserScriptJob::activeScriptList() const { return mActiveScripts; } QString ParseUserScriptJob::error() const { return mError; } +void ParseUserScriptJob::kill() +{ + if ( mSieveJob ) + mSieveJob->kill(); + mSieveJob = 0; +} + + QStringList ParseUserScriptJob::extractActiveScript(const QDomDocument &doc) { QStringList lstScript; QDomElement docElem = doc.documentElement(); QDomNode n = docElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { const QString tagName = e.tagName(); if (tagName == QLatin1String("action")) { if (e.hasAttribute(QLatin1String("name"))) { const QString actionName = e.attribute(QLatin1String("name")); if (actionName == QLatin1String("include")) { //Load includes const QString str = loadInclude(e); if (!str.isEmpty()) { if (!lstScript.contains(str)) { lstScript.append(str); } } } } } } n = n.nextSibling(); } return lstScript; } QString ParseUserScriptJob::loadInclude(const QDomElement &element) { QString scriptName; QDomNode node = element.firstChild(); while (!node.isNull()) { QDomElement e = node.toElement(); if (!e.isNull()) { const QString tagName = e.tagName(); if (tagName == QLatin1String("str")) { scriptName = e.text(); } } node = node.nextSibling(); } return scriptName; } diff --git a/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.h b/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.h index 595ef66643..435ec65685 100644 --- a/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.h +++ b/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.h @@ -1,65 +1,68 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #ifndef PARSEUSERSCRIPTJOB_H #define PARSEUSERSCRIPTJOB_H #include #include #include #include "ksieveui_export.h" class QDomDocument; class QDomElement; namespace KManageSieve { class SieveJob; } namespace KSieveUi { class KSIEVEUI_EXPORT ParseUserScriptJob : public QObject { Q_OBJECT + + friend class ParseUserJobTest; public: explicit ParseUserScriptJob(const KUrl &url,QObject *parent=0); ~ParseUserScriptJob(); void start(); KUrl scriptUrl() const; QStringList activeScriptList() const; QString error() const; + void kill(); private Q_SLOTS: void slotGetResult( KManageSieve::SieveJob *, bool, const QString &, bool ); Q_SIGNALS: void finished(ParseUserScriptJob* job); private: void emitSuccess(const QStringList &activeScriptList); void emitError(const QString &msgError); static QString loadInclude(const QDomElement &element); static QStringList extractActiveScript(const QDomDocument &doc); static QStringList parsescript(const QString &script, bool &result); KUrl mCurrentUrl; KManageSieve::SieveJob *mSieveJob; QStringList mActiveScripts; QString mError; }; } #endif // PARSEUSERSCRIPTJOB_H diff --git a/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.cpp b/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.cpp index 2f39ffa8ad..c699e938ef 100644 --- a/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.cpp +++ b/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.cpp @@ -1,105 +1,106 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "parseuserjobtest.h" #include "ksieveui/managescriptsjob/parseuserscriptjob.h" #include +using namespace KSieveUi; -QTEST_KDEMAIN( ParseUserTest, NoGUI ) +QTEST_KDEMAIN( ParseUserJobTest, NoGUI ) -void ParseUserTest::testParseEmptyUserJob() +void ParseUserJobTest::testParseEmptyUserJob() { const QString script; bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 0); QCOMPARE(result, true); } -void ParseUserTest::testParseUserTwoActiveScriptJob() +void ParseUserJobTest::testParseUserTwoActiveScriptJob() { const QString script = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "require [\"include\"];\n" "include :personal \"file1\";\n" "include :personal \"file2\";\n"); bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 2); QCOMPARE(result, true); } -void ParseUserTest::testParseUserNoActiveScriptJob() +void ParseUserJobTest::testParseUserNoActiveScriptJob() { const QString script = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "require [\"include\"];\n"); bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 0); QCOMPARE(result, true); } -void ParseUserTest::testParseUserDuplicateActiveScriptJob() +void ParseUserJobTest::testParseUserDuplicateActiveScriptJob() { const QString script = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "require [\"include\"];\n" "include :personal \"file1\";\n" "include :personal \"file1\";\n"); bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 1); QCOMPARE(result, true); } -void ParseUserTest::testParseUserErrorScriptJob() +void ParseUserJobTest::testParseUserErrorScriptJob() { const QString script = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "errorscript\n"); bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 0); QCOMPARE(result, false); } diff --git a/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.h b/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.h index 3a62665ce5..7806c218b4 100644 --- a/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.h +++ b/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.h @@ -1,33 +1,35 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #ifndef PARSEUSERJOBTEST_H #define PARSEUSERJOBTEST_H #include -class ParseUserTest : public QObject + +namespace KSieveUi { +class ParseUserJobTest : public QObject { Q_OBJECT private Q_SLOTS: void testParseEmptyUserJob(); void testParseUserTwoActiveScriptJob(); void testParseUserNoActiveScriptJob(); void testParseUserDuplicateActiveScriptJob(); void testParseUserErrorScriptJob(); }; - +} #endif // PARSEUSERJOBTEST_H diff --git a/libksieve/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp b/libksieve/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp index 0fa9c34144..65c382c5b8 100644 --- a/libksieve/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp +++ b/libksieve/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp @@ -1,215 +1,215 @@ /* Copyright (c) 2012, 2013 Montel Laurent This program 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. 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 */ #include "xmlprintingscriptbuilder.h" #include using KSieve::Parser; #include #include using namespace KSieveUi; XMLPrintingScriptBuilder::XMLPrintingScriptBuilder() : KSieve::ScriptBuilder(), mIsAction(false) { write( QLatin1String("") ); write( QLatin1String("") ); } void XMLPrintingScriptBuilder::write( const QString &msg ) { mResult += msg; } void XMLPrintingScriptBuilder::write( const QString & key, const QString & value ) { if ( value.isEmpty() ) { write( QString::fromLatin1("<%1>").arg(key) ); return; } write( QString::fromLatin1("<%1>").arg(key) ); write( value ); write( QString::fromLatin1("").arg(key) ); } void XMLPrintingScriptBuilder::write( const QString & key, const QString &attribute, const QString & value ) { if ( value.isEmpty() ) { write( QString::fromLatin1("<%1/>").arg(key) ); return; } if (attribute.isEmpty()) write( QString::fromLatin1("<%1>").arg(key) ); else write( QString::fromLatin1("<%1 %2>").arg(key).arg(attribute) ); write( value ); write( QString::fromLatin1("").arg(key) ); } QString XMLPrintingScriptBuilder::result() const { return mResult; } QString XMLPrintingScriptBuilder::error() const { return mError; } bool XMLPrintingScriptBuilder::hasError() const { return !mError.isEmpty(); } void XMLPrintingScriptBuilder::clear() { mResult.clear(); mError.clear(); } QDomDocument XMLPrintingScriptBuilder::toDom() const { QString errorMsg; int errorRow; int errorCol; QDomDocument doc; if ( !doc.setContent( mResult, &errorMsg, &errorRow, &errorCol ) ) { kDebug() << "Unable to load document.Parse error in line " << errorRow << ", col " << errorCol << ": " << errorMsg; kDebug()<<" mResult"< This program 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. 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 */ #ifndef KSIEVE_KSIEVEUI_XMLPRINTINGSCRIPTBUILDER_H #define KSIEVE_KSIEVEUI_XMLPRINTINGSCRIPTBUILDER_H #include "ksieveui_export.h" #include #include namespace KSieveUi { class KSIEVEUI_EXPORT XMLPrintingScriptBuilder : public KSieve::ScriptBuilder { public: explicit XMLPrintingScriptBuilder(); ~XMLPrintingScriptBuilder(); void taggedArgument( const QString &tag ); void stringArgument( const QString &string, bool multiLine, const QString & /*fixme*/ ); void numberArgument( unsigned long number, char quantifier ); - void commandStart( const QString &identifier ); - void commandEnd(); + void commandStart( const QString &identifier, int lineNumber); + void commandEnd(int lineNumber); void testStart( const QString &identifier ); void testEnd(); void testListStart(); void testListEnd(); - void blockStart(); - void blockEnd(); + void blockStart(int lineNumber); + void blockEnd(int lineNumber); void stringListArgumentStart(); void stringListArgumentEnd(); void stringListEntry( const QString &string, bool multiline, const QString &hashComment ); void hashComment( const QString &comment ); void bracketComment( const QString &comment ); void lineFeed(); void error( const KSieve::Error &error ); void finished(); QString result() const; QString error() const; bool hasError() const; void clear(); QDomDocument toDom() const; private: void write( const QString &msg ); void write( const QString &key, const QString &value ); void write( const QString &key, const QString &attribute, const QString & value ); QString mResult; QString mError; bool mIsAction; }; } #endif diff --git a/libksieve/ksieveui/vacation/multiimapvacationmanager.cpp b/libksieve/ksieveui/vacation/multiimapvacationmanager.cpp index 563b675ed9..ed48d2b24f 100644 --- a/libksieve/ksieveui/vacation/multiimapvacationmanager.cpp +++ b/libksieve/ksieveui/vacation/multiimapvacationmanager.cpp @@ -1,123 +1,135 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "multiimapvacationmanager.h" #include "vacationcheckjob.h" #include "util/util.h" #include #include #include #include #include #include +#include + using namespace KSieveUi; MultiImapVacationManager::MultiImapVacationManager(QObject *parent) : QObject(parent), mNumberOfJobs(0), mCheckInProgress(false) { } MultiImapVacationManager::~MultiImapVacationManager() { } QMap MultiImapVacationManager::serverList() { QMap list; const Akonadi::AgentInstance::List instances = KSieveUi::Util::imapAgentInstances(); foreach ( const Akonadi::AgentInstance &instance, instances ) { if ( instance.status() == Akonadi::AgentInstance::Broken ) continue; const KUrl url = KSieveUi::Util::findSieveUrlForAccount( instance.identifier() ); if ( !url.isEmpty() ) { list.insert(instance.name(), url); } } return list; } void MultiImapVacationManager::checkVacation(const QString &serverName, const KUrl &url) { ++mNumberOfJobs; if (!mKep14Support.contains(serverName)) { CheckKep14SupportJob *checkKep14Job = new CheckKep14SupportJob(this); checkKep14Job->setProperty(QLatin1String("triggerScript").latin1(), true); checkKep14Job->setServerName(serverName); checkKep14Job->setServerUrl(url); connect(checkKep14Job, SIGNAL(result(CheckKep14SupportJob*,bool)), SLOT(slotCheckKep14Ended(CheckKep14SupportJob*,bool))); checkKep14Job->start(); } VacationCheckJob *job = new VacationCheckJob(url, serverName, this); job->setKep14Support(mKep14Support[serverName]); connect(job, SIGNAL(scriptActive(VacationCheckJob*,QString,bool)), this, SLOT(slotScriptActive(VacationCheckJob*,QString,bool))); job->start(); } void MultiImapVacationManager::checkVacation() { if (mCheckInProgress) return; mNumberOfJobs = 0; mCheckInProgress = true; QMap list = serverList(); foreach ( const QString &serverName, list.keys() ) { const KUrl url = list.value(serverName); checkVacation(serverName, url); } } void MultiImapVacationManager::slotScriptActive(VacationCheckJob* job, QString scriptName, bool active) { --mNumberOfJobs; if (mNumberOfJobs == 0) { mCheckInProgress = false; } job->deleteLater(); if (job->noScriptFound()) { emit scriptActive(false, job->serverName()); return; } emit scriptActive(active, job->serverName()); emit scriptAvailable(job->serverName(), job->sieveCapabilities(), scriptName, job->script(), active); } void MultiImapVacationManager::slotCheckKep14Ended(CheckKep14SupportJob *job, bool success) { job->deleteLater(); if (!success) { --mNumberOfJobs; return; } mKep14Support.insert(job->serverName(), job->hasKep14Support()); VacationCheckJob *checkJob = new VacationCheckJob(job->serverUrl(), job->serverName(), this); checkJob->setKep14Support(job->hasKep14Support()); connect(checkJob, SIGNAL(scriptActive(VacationCheckJob*,QString,bool)), SLOT(slotScriptActive(VacationCheckJob*,QString,bool))); checkJob->start(); } + +bool MultiImapVacationManager::kep14Support(QString serverName) +{ + if (mKep14Support.contains(serverName)) { + return mKep14Support[serverName]; + } else { + kWarning() << "We don't know the KEP:14 support for this server." << serverName; + } + return false; +} diff --git a/libksieve/ksieveui/vacation/multiimapvacationmanager.h b/libksieve/ksieveui/vacation/multiimapvacationmanager.h index aee0c83a32..ca46b38b3a 100644 --- a/libksieve/ksieveui/vacation/multiimapvacationmanager.h +++ b/libksieve/ksieveui/vacation/multiimapvacationmanager.h @@ -1,57 +1,59 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #ifndef MULTIIMAPVACATIONMANAGER_H #define MULTIIMAPVACATIONMANAGER_H #include #include #include "ksieveui_export.h" class KUrl; namespace KSieveUi { class CheckKep14SupportJob; class VacationCheckJob; class KSIEVEUI_EXPORT MultiImapVacationManager : public QObject { Q_OBJECT public: explicit MultiImapVacationManager(QObject *parent=0); ~MultiImapVacationManager(); void checkVacation(); QMap serverList(); void checkVacation(const QString &serverName, const KUrl &url); + bool kep14Support(QString serverName); + Q_SIGNALS: void scriptActive(bool active, const QString &serverName); void scriptAvailable(const QString &serverName, const QStringList &sieveCapabilities, const QString &scriptName, const QString &script, bool active); private slots: void slotScriptActive(VacationCheckJob* job, QString scriptName, bool active); void slotCheckKep14Ended(CheckKep14SupportJob *job, bool success); private: int mNumberOfJobs; bool mCheckInProgress; QMap mKep14Support; //if the server has KEP:14 support }; } #endif // MULTIIMAPVACATIONMANAGER_H diff --git a/libksieve/ksieveui/vacation/tests/CMakeLists.txt b/libksieve/ksieveui/vacation/tests/CMakeLists.txt index 546a63f1d8..200e679e2f 100644 --- a/libksieve/ksieveui/vacation/tests/CMakeLists.txt +++ b/libksieve/ksieveui/vacation/tests/CMakeLists.txt @@ -1,12 +1,36 @@ include_directories(${CMAKE_SOURCE_DIR}/libksieve ${CMAKE_SOURCE_DIR}/libksieve/ksieveui + ${CMAKE_BINARY_DIR}/libksieve + ${CMAKE_BINARY_DIR}/libksieve/ksieveui ) set(vacation_multi_server_SRCS main.cpp ) KDE4_ADD_EXECUTABLE(vacationmultiservertest ${vacation_multi_server_SRCS} ) TARGET_LINK_LIBRARIES(vacationmultiservertest ${KDE4_KDEUI_LIBS} ksieveui ksieve) +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +macro( add_vacation_test _source ) + set( _test ${_source}test.cpp + ../${_source}.cpp + ../vacationscriptextractor.cpp + ${CMAKE_BINARY_DIR}/libksieve/ksieveui/sieve-vacation.cpp + ${CMAKE_SOURCE_DIR}/libksieve/parser/lexer.cpp + ${CMAKE_SOURCE_DIR}/libksieve/parser/parser.cpp + ${CMAKE_SOURCE_DIR}/libksieve/parser/utf8validator.cpp + ) + get_filename_component( _name ${_source} NAME_WE ) + kde4_add_unit_test( ${_name} TESTNAME vacation-${_name} ${_test} ) + set_target_properties(${_name} PROPERTIES COMPILE_FLAGS -DVACATIONTESTDATADIR="\\"${CMAKE_CURRENT_SOURCE_DIR}/data/\\"") + target_link_libraries( ${_name} + ksieveui + kmanagesieve + ksieve + ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDEPIMLIBS_KMIME_LIBS} ${KDEPIMLIBS_KPIMIDENTITIES_LIBS}) +endmacro() + +add_vacation_test( vacationutils ) \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-complex.siv b/libksieve/ksieveui/vacation/tests/data/vacation-complex.siv new file mode 100644 index 0000000000..b7b4140bcf --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-complex.siv @@ -0,0 +1,8 @@ +require ["date","relational","vacation"]; +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if allof (currentdate :zone "+0100" :value "ge" "date" "2015-01-02", currentdate :zone "+0100" :value "le" "date" "2015-03-04", not header :contains "X-Spam-Flag" "YES") +{ + vacation :days 7 :addresses "test@test.de" :subject "XXX" "dsfgsdfgsdfg"; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-complex.siv b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-complex.siv new file mode 100644 index 0000000000..bbd94ab272 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-complex.siv @@ -0,0 +1,8 @@ +require ["date","relational","vacation"]; +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if false # allof (currentdate :zone "+0100" :value "ge" "date" "2015-01-02", currentdate :zone "+0100" :value "le" "date" "2015-03-04", not header :contains "X-Spam-Flag" "YES") +{ + vacation :days 7 :addresses "test@test.de" :subject "XXX" "dsfgsdfgsdfg"; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-multiple.siv b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-multiple.siv new file mode 100644 index 0000000000..1889dc2269 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-multiple.siv @@ -0,0 +1,19 @@ +require ["vacation"]; + +if true +{ + testcommand; +} + +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if false # true +{ + vacation :subject "XXX" "dsfgsdfgsdfg"; +} + +if true +{ + testcommand; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-deactivate.siv b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate.siv new file mode 100644 index 0000000000..c2b15a22db --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate.siv @@ -0,0 +1,9 @@ +require ["vacation"]; + +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if false # true +{ + vacation :subject "XXX" "dsfgsdfgsdfg"; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-multiple.siv b/libksieve/ksieveui/vacation/tests/data/vacation-multiple.siv new file mode 100644 index 0000000000..7737faf738 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-multiple.siv @@ -0,0 +1,16 @@ +require ["vacation"]; + +if false +{ + testcommand; +} + +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +vacation :subject "XXX" "dsfgsdfgsdfg"; + +if false +{ + testcommand; +} diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-notfound.siv b/libksieve/ksieveui/vacation/tests/data/vacation-notfound.siv new file mode 100644 index 0000000000..ce93aeefb2 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-notfound.siv @@ -0,0 +1,15 @@ +#blabla + +testcommand; + +if true { + testcmd2; +} + +if false { + testcmd3; +} + +if true { + testcmd4; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-simple.siv b/libksieve/ksieveui/vacation/tests/data/vacation-simple.siv new file mode 100644 index 0000000000..ab6c321800 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-simple.siv @@ -0,0 +1,6 @@ +require ["vacation"]; + +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +vacation :subject "XXX" "dsfgsdfgsdfg"; \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/vacationutilstest.cpp b/libksieve/ksieveui/vacation/tests/vacationutilstest.cpp new file mode 100644 index 0000000000..f0cb13da82 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/vacationutilstest.cpp @@ -0,0 +1,303 @@ +/* + Copyright (c) 2015 Sandro Knauß + + This program 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. + + 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 +*/ +#include "vacationutilstest.h" +#include "vacation/vacationutils.h" + +#include + +#include +#include +#include + +using namespace KSieveUi; + +QTEST_KDEMAIN( VacationUtilsTest, NoGUI ) + +void VacationUtilsTest::testParseEmptyScript() +{ + const QString script; + QCOMPARE(VacationUtils::foundVacationScript(script), false); +} + +void VacationUtilsTest::testParseOnlyComment() +{ + QString script(QLatin1String("#comment")); + QCOMPARE(VacationUtils::foundVacationScript(script), false); + script = QLatin1String("#comment\n\n#comment\n"); + QCOMPARE(VacationUtils::foundVacationScript(script), false); +} + +void VacationUtilsTest::testParseActivate_data() +{ + QTest::addColumn("filename"); + QTest::addColumn("found"); + QTest::addColumn("active"); + + QTest::newRow("notfound") << QString::fromLatin1("vacation-notfound.siv") << false << false; + QTest::newRow("simple") << QString::fromLatin1("vacation-simple.siv") << true << true; + QTest::newRow("multile if") << QString::fromLatin1("vacation-multiple.siv") << true << true; + QTest::newRow("deactivate") << QString::fromLatin1("vacation-deactivate.siv") << true << false; + QTest::newRow("deactivate-multiple if") << QString::fromLatin1("vacation-deactivate-multiple.siv") << true << false; + QTest::newRow("deactivate-complex") << QString::fromLatin1("vacation-deactivate-complex.siv") << true << false; +} + + +void VacationUtilsTest::testParseActivate() +{ + QFETCH(QString, filename); + QFETCH(bool, found); + QFETCH(bool, active); + + QFile file(QLatin1String(VACATIONTESTDATADIR)+filename); + QVERIFY(file.open(QIODevice::ReadOnly)); + QString script = QString::fromUtf8(file.readAll()); + QCOMPARE(VacationUtils::foundVacationScript(script), found); + + QString messageText; + QString subject; + int notificationInterval; + QStringList aliases; + bool sendForSpam; + QString domainName; + QDate startDate; + QDate endDate; + bool scriptActive = !active; + + bool ret = VacationUtils::parseScript(script, scriptActive, messageText, subject, notificationInterval, aliases, sendForSpam, domainName, startDate, endDate); + QCOMPARE(ret, found); + QCOMPARE(scriptActive, active); +} + +void VacationUtilsTest::testParseScript_data() +{ + QTest::addColumn("activate"); + QTest::addColumn("deactivate"); + + QTest::newRow("simple") << QString::fromLatin1("vacation-simple.siv") << QString::fromLatin1("vacation-deactivate.siv"); + QTest::newRow("complex") << QString::fromLatin1("vacation-complex.siv") << QString::fromLatin1("vacation-deactivate-complex.siv"); +} + + +void VacationUtilsTest::testParseScript() +{ + QFETCH(QString, activate); + QFETCH(QString, deactivate); + QFile fileA(QLatin1String(VACATIONTESTDATADIR) + activate); + QVERIFY(fileA.open(QIODevice::ReadOnly)); + QString scriptA = QString::fromUtf8(fileA.readAll()); + QFile fileD(QLatin1String(VACATIONTESTDATADIR) + deactivate); + QVERIFY(fileD.open(QIODevice::ReadOnly)); + QString scriptD = QString::fromUtf8(fileD.readAll()); + + QString messageTextA, messageTextD; + QString subjectA, subjectD; + int notificationIntervalA, notificationIntervalD; + QStringList aliasesA, aliasesD; + bool sendForSpamA, sendForSpamD; + QString domainNameA, domainNameD; + QDate startDateA, startDateD; + QDate endDateA, endDateD; + bool scriptActiveA, scriptActiveD; + VacationUtils::parseScript(scriptA, scriptActiveA, messageTextA, subjectA, notificationIntervalA, aliasesA, sendForSpamA, domainNameA, startDateA, endDateA); + VacationUtils::parseScript(scriptD, scriptActiveD, messageTextD, subjectD, notificationIntervalD, aliasesD, sendForSpamD, domainNameD, startDateD, endDateD); + QCOMPARE(scriptActiveA, true); + QCOMPARE(scriptActiveD, false); + QCOMPARE(messageTextD, messageTextA); + QCOMPARE(subjectD, subjectA); + QCOMPARE(notificationIntervalD, notificationIntervalA); + QCOMPARE(aliasesD, aliasesA); + QCOMPARE(sendForSpamD, sendForSpamA); + QCOMPARE(domainNameD, domainNameA); + QCOMPARE(startDateD, startDateA); + QCOMPARE(endDateD, endDateA); +} + +void VacationUtilsTest::testParseScriptComplex() +{ + QFile file(QLatin1String(VACATIONTESTDATADIR "vacation-complex.siv")); + QVERIFY(file.open(QIODevice::ReadOnly)); + QString script = QString::fromUtf8(file.readAll()); + + QString messageText; + QString subject; + int notificationInterval; + QStringList aliases; + bool sendForSpam; + QString domainName; + QDate startDate; + QDate endDate; + bool scriptActive; + VacationUtils::parseScript(script, scriptActive, messageText, subject, notificationInterval, aliases, sendForSpam, domainName, startDate, endDate); + QCOMPARE(scriptActive, true); + QCOMPARE(messageText, QLatin1String("dsfgsdfgsdfg")); + QCOMPARE(subject, QLatin1String("XXX")); + QCOMPARE(notificationInterval, 7); + QCOMPARE(aliases, QStringList() << QLatin1String("test@test.de")); + QCOMPARE(sendForSpam, false); + QCOMPARE(domainName, QString()); + QCOMPARE(startDate, QDate(2015, 01, 02)); + QCOMPARE(endDate, QDate(2015, 03, 04)); +} + +void VacationUtilsTest::testWriteScript() +{ + QString messageText(QLatin1String("dsfgsdfgsdfg")); + QString subject(QLatin1String("XXX")); + int notificationInterval(7); + QStringList aliases = QStringList() << QLatin1String("test@test.de"); + QList addesses; + bool sendForSpam(false); + QString domainName(QLatin1String("example.org")); + QDate startDate(2015, 01, 02); + QDate endDate(2015, 03, 04); + bool scriptActive(true); + + QString messageTextA; + QString subjectA; + int notificationIntervalA; + QStringList aliasesA; + bool sendForSpamA; + QString domainNameA; + QDate startDateA; + QDate endDateA; + bool scriptActiveA; + + foreach(const QString &alias, aliases) { + KMime::Types::Mailbox a; + a.fromUnicodeString(alias); + addesses.append(a.addrSpec()); + } + + QString script = VacationUtils::composeScript(messageText, scriptActive, subject, notificationInterval, addesses, sendForSpam, domainName, startDate, endDate); + bool ret = VacationUtils::parseScript(script, scriptActiveA, messageTextA, subjectA, notificationIntervalA, aliasesA, sendForSpamA, domainNameA, startDateA, endDateA); + QCOMPARE(ret, true); + QCOMPARE(scriptActiveA, scriptActive); + QCOMPARE(messageTextA, messageText); + QCOMPARE(subjectA, subject); + QCOMPARE(notificationIntervalA, notificationInterval); + QCOMPARE(aliasesA, aliases); + QCOMPARE(sendForSpamA, sendForSpam); + QCOMPARE(domainNameA, domainName); + QCOMPARE(startDateA, startDate); + QCOMPARE(endDateA, endDate); + + scriptActive = false; + script = VacationUtils::composeScript(messageText, scriptActive, subject, notificationInterval, addesses, sendForSpam, domainName, startDate, endDate); + ret = VacationUtils::parseScript(script, scriptActiveA, messageTextA, subjectA, notificationIntervalA, aliasesA, sendForSpamA, domainNameA, startDateA, endDateA); + QCOMPARE(ret, true); + QCOMPARE(scriptActiveA, scriptActive); + QCOMPARE(messageTextA, messageText); + QCOMPARE(subjectA, subject); + QCOMPARE(notificationIntervalA, notificationInterval); + QCOMPARE(aliasesA, aliases); + QCOMPARE(sendForSpamA, sendForSpam); + QCOMPARE(domainNameA, domainName); + QCOMPARE(startDateA, startDate); + QCOMPARE(endDateA, endDate); +} + + +void VacationUtilsTest::testWriteSimpleScript() +{ + QString messageText(QLatin1String("dsfgsdfgsdfg")); + QString subject(QLatin1String("XXX")); + int notificationInterval(7); + bool scriptActive(true); + + QString messageTextA; + QString subjectA; + int notificationIntervalA; + QStringList aliasesA; + bool sendForSpamA; + QString domainNameA; + QDate startDateA; + QDate endDateA; + bool scriptActiveA; + + QString script = VacationUtils::composeScript(messageText, scriptActive, subject, notificationInterval, QList(), true, QString(), QDate(), QDate()); + bool ret = VacationUtils::parseScript(script, scriptActiveA, messageTextA, subjectA, notificationIntervalA, aliasesA, sendForSpamA, domainNameA, startDateA, endDateA); + QCOMPARE(ret, true); + QCOMPARE(scriptActiveA, scriptActive); + QCOMPARE(messageTextA, messageText); + QCOMPARE(subjectA, subject); + QCOMPARE(notificationIntervalA, notificationInterval); + + scriptActive = false; + script = VacationUtils::composeScript(messageText, scriptActive, subject, notificationInterval, QList(), true, QString(), QDate(), QDate()); + ret = VacationUtils::parseScript(script, scriptActiveA, messageTextA, subjectA, notificationIntervalA, aliasesA, sendForSpamA, domainNameA, startDateA, endDateA); + QCOMPARE(ret, true); + QCOMPARE(scriptActiveA, scriptActive); + QCOMPARE(messageTextA, messageText); + QCOMPARE(subjectA, subject); + QCOMPARE(notificationIntervalA, notificationInterval); +} + +void VacationUtilsTest::testUpdateVacationBlock() +{ + QFile fileA(QLatin1String(VACATIONTESTDATADIR "vacation-simple.siv")); + QVERIFY(fileA.open(QIODevice::ReadOnly)); + QString scriptA = QString::fromUtf8(fileA.readAll()); + + QFile fileB(QLatin1String(VACATIONTESTDATADIR "vacation-deactivate.siv")); + QVERIFY(fileB.open(QIODevice::ReadOnly)); + QString scriptB = QString::fromUtf8(fileB.readAll()); + + const QString attend = QLatin1String("if true\n{\ntestcmd;\n}\n"); + const QString require = QLatin1String("require [\"date\", \"test\"];"); + const QString scriptAattend = scriptA + QLatin1String("\n") + attend; + const QString scriptBattend = scriptB + QLatin1String("\n") + attend; + + QStringList linesA = scriptA.split(QLatin1Char('\n')); + QStringList header; + for(int i=0; i<5;i++ ){ + header.append(linesA.at(i)); + } + + QStringList vacation; + for(int i=5; i + Copyright (c) 2015 Sandro Knauß This program 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. 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 */ -#ifndef PARSEUSERJOBTEST_H -#define PARSEUSERJOBTEST_H +#ifndef VACATIONUTILSTEST_H +#define VACATIONUTILSTEST_H #include -class ParseUserTest : public QObject + +namespace KSieveUi { +class VacationUtilsTest : public QObject { Q_OBJECT private Q_SLOTS: - void testParseEmptyUserJob(); - void testParseUserTwoActiveScriptJob(); - void testParseUserNoActiveScriptJob(); - void testParseUserDuplicateActiveScriptJob(); - void testParseUserErrorScriptJob(); + void testParseEmptyScript(); + void testParseOnlyComment(); + void testParseActivate_data(); + void testParseActivate(); + void testParseScript_data(); + void testParseScript(); + void testParseScriptComplex(); + void testWriteScript(); + void testWriteSimpleScript(); + void testUpdateVacationBlock(); + void testMergeRequireLine(); }; - -#endif // PARSEUSERJOBTEST_H +} +#endif // VACATIONUTILSTEST_H diff --git a/libksieve/ksieveui/vacation/vacation.cpp b/libksieve/ksieveui/vacation/vacation.cpp index 96689fa4bb..0fe799a7bc 100644 --- a/libksieve/ksieveui/vacation/vacation.cpp +++ b/libksieve/ksieveui/vacation/vacation.cpp @@ -1,228 +1,230 @@ /* -*- c++ -*- vacation.cpp KMail, the KDE mail client. Copyright (c) 2002 Marc Mutz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. 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, US */ #include "vacation.h" #include "vacationutils.h" #include "vacationscriptextractor.h" #include "sieve-vacation.h" #include "util/util.h" #include "vacationdialog.h" #include #include #include #include #include #include #include #include using namespace KSieveUi; Vacation::Vacation(QObject * parent, bool checkOnly, const KUrl &url) : QObject( parent ), mSieveJob( 0 ), mDialog( 0 ), mWasActive( false ), mCheckOnly( checkOnly ) { if (url.isEmpty()) { mUrl = findURL(mServerName); } else { mUrl = url; } kDebug() << "Vacation: found url \"" << mUrl.prettyUrl() <<"\""; if ( mUrl.isEmpty() ) // nothing to do... return; mSieveJob = KManageSieve::SieveJob::get( mUrl ); if (checkOnly) { mSieveJob->setInteractive( false ); } connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool)) ); } Vacation::~Vacation() { if ( mSieveJob ) mSieveJob->kill(); mSieveJob = 0; delete mDialog; mDialog = 0; kDebug() << "~Vacation()"; } KUrl Vacation::findURL(QString &serverName) const { const Akonadi::AgentInstance::List instances = Util::imapAgentInstances(); foreach ( const Akonadi::AgentInstance &instance, instances ) { if ( instance.status() == Akonadi::AgentInstance::Broken ) continue; const KUrl url = Util::findSieveUrlForAccount( instance.identifier() ); if ( !url.isEmpty() ) { serverName = instance.name(); return url; } } return KUrl(); } void Vacation::slotGetResult( KManageSieve::SieveJob * job, bool success, const QString & script, bool active ) { kDebug() << success << ", ?," << active << ")" << endl << "script:" << endl << script; mSieveJob = 0; // job deletes itself after returning from this slot! if ( !mCheckOnly && mUrl.protocol() == QLatin1String("sieve") && !job->sieveCapabilities().contains(QLatin1String("vacation")) ) { KMessageBox::sorry( 0, i18n( "Your server did not list \"vacation\" in " "its list of supported Sieve extensions;\n" "without it, KMail cannot install out-of-" "office replies for you.\n" "Please contact your system administrator." ) ); emit result( false ); return; } const bool supportsDate = job->sieveCapabilities().contains(QLatin1String("date")); if ( !mDialog && !mCheckOnly ) mDialog = new VacationDialog( i18n("Configure \"Out of Office\" Replies"), 0, false ); QString messageText = VacationUtils::defaultMessageText(); QString subject = VacationUtils::defaultSubject(); int notificationInterval = VacationUtils::defaultNotificationInterval(); QStringList aliases = VacationUtils::defaultMailAliases(); bool sendForSpam = VacationUtils::defaultSendForSpam(); QString domainName = VacationUtils::defaultDomainName(); QDate startDate = VacationUtils::defaultStartDate(); QDate endDate = VacationUtils::defaultEndDate(); + bool sActive = true; + if ( !success ) active = false; // default to inactive - if ( !mCheckOnly && ( !success || !KSieveUi::VacationUtils::parseScript( script, messageText, subject, notificationInterval, aliases, sendForSpam, domainName, startDate, endDate ) ) ) + if ( !mCheckOnly && ( !success || !KSieveUi::VacationUtils::parseScript( script, sActive, messageText, subject, notificationInterval, aliases, sendForSpam, domainName, startDate, endDate ) ) ) KMessageBox::information( 0, i18n("Someone (probably you) changed the " "vacation script on the server.\n" "KMail is no longer able to determine " "the parameters for the autoreplies.\n" "Default values will be used." ) ); - mWasActive = active; + mWasActive = active && sActive; if ( mDialog ) { - mDialog->setActivateVacation( active ); + mDialog->setActivateVacation( active && sActive ); mDialog->setSubject(subject); mDialog->setMessageText( messageText ); mDialog->setNotificationInterval( notificationInterval ); mDialog->setMailAliases( aliases.join(QLatin1String(", ")) ); mDialog->setSendForSpam( sendForSpam ); mDialog->setDomainName( domainName ); mDialog->enableDomainAndSendForSpam( !VacationSettings::allowOutOfOfficeUploadButNoSettings() ); if (supportsDate) { mDialog->enableDates( supportsDate ); mDialog->setStartDate( startDate ); mDialog->setEndDate( endDate ); } connect( mDialog, SIGNAL(okClicked()), SLOT(slotDialogOk()) ); connect( mDialog, SIGNAL(cancelClicked()), SLOT(slotDialogCancel()) ); mDialog->show(); } emit scriptActive( mWasActive, mServerName ); if ( mCheckOnly && mWasActive ) { if ( KMessageBox::questionYesNo( 0, i18n( "There is still an active out-of-office reply configured.\n" "Do you want to edit it?"), i18n("Out-of-office reply still active"), KGuiItem( i18n( "Edit"), QLatin1String("document-properties") ), KGuiItem( i18n("Ignore"), QLatin1String("dialog-cancel") ) ) == KMessageBox::Yes ) { emit requestEditVacation(); } } } void Vacation::slotDialogOk() { kDebug(); // compose a new script: - const QString script = VacationUtils::composeScript( mDialog->messageText(), + const bool active = mDialog->activateVacation(); + const QString script = VacationUtils::composeScript( mDialog->messageText(), active, mDialog->subject(), mDialog->notificationInterval(), mDialog->mailAliases(), mDialog->sendForSpam(), mDialog->domainName(), mDialog->startDate(), mDialog->endDate() ); - const bool active = mDialog->activateVacation(); emit scriptActive( active, mServerName); kDebug() << "script:" << endl << script; // and commit the dialog's settings to the server: mSieveJob = KManageSieve::SieveJob::put( mUrl, script, active, mWasActive ); if ( active ) connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), SLOT(slotPutActiveResult(KManageSieve::SieveJob*,bool)) ); else connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), SLOT(slotPutInactiveResult(KManageSieve::SieveJob*,bool)) ); // destroy the dialog: mDialog->delayedDestruct(); mDialog = 0; } void Vacation::slotDialogCancel() { kDebug(); mDialog->delayedDestruct(); mDialog = 0; emit result( false ); } void Vacation::slotPutActiveResult( KManageSieve::SieveJob * job, bool success ) { handlePutResult( job, success, true ); } void Vacation::slotPutInactiveResult( KManageSieve::SieveJob * job, bool success ) { handlePutResult( job, success, false ); } void Vacation::handlePutResult( KManageSieve::SieveJob *, bool success, bool activated ) { if ( success ) KMessageBox::information( 0, activated ? i18n("Sieve script installed successfully on the server.\n" "Out of Office reply is now active.") : i18n("Sieve script installed successfully on the server.\n" "Out of Office reply has been deactivated.") ); kDebug() << "( ???," << success << ", ? )"; mSieveJob = 0; // job deletes itself after returning from this slot! emit result( success ); emit scriptActive( activated, mServerName ); } void Vacation::showVacationDialog() { if (mDialog) { mDialog->show(); mDialog->raise(); mDialog->activateWindow(); } } diff --git a/libksieve/ksieveui/vacation/vacationcheckjob.cpp b/libksieve/ksieveui/vacation/vacationcheckjob.cpp index b323e96c7a..6f594dbcbc 100644 --- a/libksieve/ksieveui/vacation/vacationcheckjob.cpp +++ b/libksieve/ksieveui/vacation/vacationcheckjob.cpp @@ -1,196 +1,211 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "vacationcheckjob.h" #include "vacationutils.h" #include #include #include #include using namespace KSieveUi; VacationCheckJob::VacationCheckJob(const KUrl &url, const QString &serverName, QObject *parent) : QObject(parent), mServerName(serverName), mUrl(url) , mKep14Support(false) , mSieveJob(0) , mParseJob(0) , mNoScriptFound(0) { } VacationCheckJob::~VacationCheckJob() +{ + kill(); +} + +void VacationCheckJob::kill() { if ( mSieveJob ) mSieveJob->kill(); mSieveJob = 0; + + if (mParseJob) { + mParseJob->kill(); + } + mParseJob = 0; } + void VacationCheckJob::setKep14Support(bool kep14Support) { mKep14Support = kep14Support; } void VacationCheckJob::start() { if (mKep14Support) { KUrl url = mUrl; url.setFileName(QLatin1String("USER")); mParseJob = new ParseUserScriptJob(url); connect(mParseJob, SIGNAL(finished(ParseUserScriptJob*)), SLOT(slotGotActiveScripts(ParseUserScriptJob*))); mParseJob->start(); mSieveJob = KManageSieve::SieveJob::list(url); connect(mSieveJob, SIGNAL(gotList(KManageSieve::SieveJob*,bool,QStringList,QString)), this, SLOT(slotGotList(KManageSieve::SieveJob*,bool,QStringList,QString))); } else { mSieveJob = KManageSieve::SieveJob::get(mUrl); mSieveJob->setInteractive(false); connect(mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool))); } } void VacationCheckJob::slotGetResult(KManageSieve::SieveJob */*job*/, bool success, const QString &script, bool active) { mScript = script; mSieveCapabilities = mSieveJob->sieveCapabilities(); mSieveJob = 0; if (mKep14Support) { if (isVacationScipt(script)) { const QString &scriptName = mAvailableScripts[mScriptPos-1]; emit scriptActive(this, scriptName, mActiveScripts.contains(scriptName)); kDebug() << "vacation script found :)"; } else if (isLastScript()) { mNoScriptFound = true; emit scriptActive(this, QString(), false); kDebug() << "no vacation script found :("; } else { getNextScript(); } } else { - if ( !success ) + if ( !success ) { active = false; // default to inactive mNoScriptFound = true; + } + if (active) { + mActiveScripts << mUrl.fileName(); + } emit scriptActive(this, mUrl.fileName(), active); } } void VacationCheckJob::slotGotActiveScripts(ParseUserScriptJob *job) { mParseJob = 0; if (!job->error().isEmpty()) { emitError(QLatin1String("ParseUserScriptJob failed:")+job->error()); return; } mActiveScripts = job->activeScriptList(); if (!mSieveJob) { searchVacationScript(); } } void VacationCheckJob::slotGotList(KManageSieve::SieveJob *job, bool success, const QStringList &availableScripts, const QString &activeScript) { mSieveJob = 0; if (!success) { emitError(QLatin1String("SieveJob list failed.")); return; } mAvailableScripts = availableScripts; if (!mParseJob) { searchVacationScript(); } } void VacationCheckJob::emitError(const QString &errorMessage) { qWarning() << errorMessage; //TODO: emit error } void VacationCheckJob::searchVacationScript() { QStringList scriptList = mActiveScripts; // Reorder script list foreach(const QString &script, mAvailableScripts) { if (!scriptList.contains(script)) { scriptList.append(script); } } mAvailableScripts = scriptList; mScriptPos = 0; getNextScript(); } void VacationCheckJob::getNextScript() { if (isLastScript()) { //TODO: no script found mNoScriptFound = true; emit scriptActive(this, QString(), false); kDebug() << "no vacation script found :("; } KUrl url = mUrl; url.setFileName(mAvailableScripts[mScriptPos]); mScriptPos += 1; if (Util::isKep14ProtectedName(url.fileName())) { getNextScript(); } mSieveJob = KManageSieve::SieveJob::get(url); mSieveJob->setInteractive(false); connect(mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool))); } bool VacationCheckJob::isLastScript() const { return mScriptPos >= mAvailableScripts.count(); } bool VacationCheckJob::isVacationScipt(const QString &script) const { return KSieveUi::VacationUtils::foundVacationScript(script); } bool VacationCheckJob::noScriptFound() { return mNoScriptFound; } QString VacationCheckJob::serverName() { return mServerName; } QString VacationCheckJob::script() { return mScript; } QStringList VacationCheckJob::sieveCapabilities() { return mSieveCapabilities; } diff --git a/libksieve/ksieveui/vacation/vacationcheckjob.h b/libksieve/ksieveui/vacation/vacationcheckjob.h index b86282209c..4596d9ac77 100644 --- a/libksieve/ksieveui/vacation/vacationcheckjob.h +++ b/libksieve/ksieveui/vacation/vacationcheckjob.h @@ -1,72 +1,73 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #ifndef VACATIONCHECKJOB_H #define VACATIONCHECKJOB_H #include #include #include namespace KManageSieve { class SieveJob; } namespace KSieveUi { class ParseUserScriptJob; class VacationCheckJob : public QObject { Q_OBJECT public: explicit VacationCheckJob(const KUrl &url, const QString &serverName, QObject *parent=0); ~VacationCheckJob(); void setKep14Support(bool kep14Support); void start(); + void kill(); bool noScriptFound(); QString script(); QStringList sieveCapabilities(); QString serverName(); Q_SIGNALS: void scriptActive(VacationCheckJob* job, const QString &sscriptName, bool active); private slots: void slotGetResult(KManageSieve::SieveJob *job, bool success, const QString &script, bool active); void slotGotActiveScripts(ParseUserScriptJob *job); void slotGotList(KManageSieve::SieveJob *job, bool success, const QStringList &availableScripts, const QString &activeScript); void emitError(const QString &errorMessage); void searchVacationScript(); void getNextScript(); bool isVacationScipt(const QString &script) const; bool isLastScript() const; private: QString mServerName; KUrl mUrl; KManageSieve::SieveJob * mSieveJob; ParseUserScriptJob *mParseJob; bool mKep14Support; QStringList mAvailableScripts; QStringList mActiveScripts; int mScriptPos; bool mNoScriptFound; QString mScript; QStringList mSieveCapabilities; }; } #endif // VACATIONCHECKJOB_H diff --git a/libksieve/ksieveui/vacation/vacationcreatescriptjob.cpp b/libksieve/ksieveui/vacation/vacationcreatescriptjob.cpp index 6d31678bba..4e76bd56b1 100644 --- a/libksieve/ksieveui/vacation/vacationcreatescriptjob.cpp +++ b/libksieve/ksieveui/vacation/vacationcreatescriptjob.cpp @@ -1,102 +1,200 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "vacationcreatescriptjob.h" +#include "vacationutils.h" +#include +#include #include #include #include #include using namespace KSieveUi; VacationCreateScriptJob::VacationCreateScriptJob(QObject *parent) : QObject(parent), mActivate(false), - mWasActive(false), - mSieveJob(0) + mScriptActive(false) + , mKep14Support(false) + , mUserJobRunning(false) + , mScriptJobRunning(false) + , mSuccess(true) + , mSieveJob(0) + , mParseUserJob(0) + , mCreateJob(0) { } VacationCreateScriptJob::~VacationCreateScriptJob() { + kill(); +} + +void VacationCreateScriptJob::kill() +{ + if (mSieveJob) { + mSieveJob->kill(); + } + mSieveJob = 0; + + if (mParseUserJob) { + mParseUserJob->kill(); + } + mParseUserJob = 0; + if (mCreateJob) { + mCreateJob->kill(); + } + mParseUserJob = 0; } + void VacationCreateScriptJob::setStatus(bool activate, bool wasActive) { mActivate = activate; - mWasActive = wasActive; + mScriptActive = wasActive; } void VacationCreateScriptJob::setServerName(const QString &servername) { mServerName = servername; } -void VacationCreateScriptJob::start() +const QString &VacationCreateScriptJob::serverName() const { - if (mUrl.isEmpty()) { - qDebug()<<" server url is empty"; - deleteLater(); - return; - } - mSieveJob = KManageSieve::SieveJob::put( mUrl, mScript, mActivate, mWasActive ); - if ( mActivate ) - connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), - SLOT(slotPutActiveResult(KManageSieve::SieveJob*,bool)) ); - else - connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), - SLOT(slotPutInactiveResult(KManageSieve::SieveJob*,bool)) ); + return mServerName; +} + +void VacationCreateScriptJob::setKep14Support(bool kep14Support) +{ + mKep14Support = kep14Support; } void VacationCreateScriptJob::setServerUrl(const KUrl &url) { mUrl = url; } void VacationCreateScriptJob::setScript(const QString &script) { mScript = script; } -void VacationCreateScriptJob::slotPutActiveResult( KManageSieve::SieveJob * job, bool success ) +void VacationCreateScriptJob::start() +{ + if (mUrl.isEmpty()) { + qDebug()<<" server url is empty"; + deleteLater(); + return; + } + + mUserJobRunning = false; + mScriptJobRunning = true; + mSieveJob = KManageSieve::SieveJob::get(mUrl); + mSieveJob->setInteractive(false); + connect(mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), + SLOT(slotGetScript(KManageSieve::SieveJob*,bool,QString,bool))); + + if (mKep14Support && mActivate && !mScriptActive) { + mUserJobRunning = true; + KUrl url = mUrl; + url.setFileName(QLatin1String("USER")); + mParseUserJob = new ParseUserScriptJob(url, this); + connect(mParseUserJob, SIGNAL(finished(ParseUserScriptJob*)), SLOT(slotGotActiveScripts(ParseUserScriptJob*))); + mParseUserJob->start(); + } +} + +void VacationCreateScriptJob::slotGetScript(KManageSieve::SieveJob *job, bool success, const QString &oldScript, bool active) { - handlePutResult( job, success, true ); + mSieveJob = 0; + QString script = mScript; + if (success || !oldScript.trimmed().isEmpty()) { + script = VacationUtils::mergeRequireLine(oldScript, mScript); + script = VacationUtils::updateVacationBlock(oldScript,mScript); + } + if (mKep14Support) { + mSieveJob = KManageSieve::SieveJob::put( mUrl, mScript, false, false ); + } else { + mSieveJob = KManageSieve::SieveJob::put( mUrl, mScript, mActivate, false ); //Never deactivate + } + connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), + SLOT(slotPutResult(KManageSieve::SieveJob*,bool)) ); } -void VacationCreateScriptJob::slotPutInactiveResult( KManageSieve::SieveJob * job, bool success ) +void VacationCreateScriptJob::slotPutResult( KManageSieve::SieveJob * job, bool success ) { - handlePutResult( job, success, false ); + mSieveJob = 0; + mScriptJobRunning = false; + if (!success) { + mSuccess = false; + } + handleResult(); } -void VacationCreateScriptJob::handlePutResult( KManageSieve::SieveJob *, bool success, bool activated ) +void VacationCreateScriptJob::handleResult() { - if ( success ) - KMessageBox::information( 0, activated + if (mUserJobRunning || mScriptJobRunning) { // Not both jobs are done + return; + } + + if ( mSuccess ) + KMessageBox::information( 0, mActivate ? i18n("Sieve script installed successfully on the server \'%1\'.\n" "Out of Office reply is now active.", mServerName) : i18n("Sieve script installed successfully on the server \'%1\'.\n" "Out of Office reply has been deactivated.", mServerName) ); - kDebug() << "( ???," << success << ", ? )"; - mSieveJob = 0; // job deletes itself after returning from this slot! - Q_EMIT result( success ); - Q_EMIT scriptActive( activated, mServerName ); + kDebug() << "( ???," << mSuccess << ", ? )"; + Q_EMIT result( mSuccess ); + Q_EMIT scriptActive( mActivate, mServerName ); deleteLater(); } + +void VacationCreateScriptJob::slotGotActiveScripts(ParseUserScriptJob *job) +{ + mParseUserJob = 0; + if (!job->error().isEmpty()) { + slotGenerateDone(job->error()); + return; + } + + QStringList list = job->activeScriptList(); + + if (!list.contains(mUrl.fileName())) { + list.prepend(mUrl.fileName()); + mCreateJob = new GenerateGlobalScriptJob(mUrl, this); + mCreateJob->addUserActiveScripts(list); + connect( mCreateJob, SIGNAL(success()), SLOT(slotGenerateDone())); + connect( mCreateJob, SIGNAL(error(QString)), SLOT(slotGenerateDone(QString))); + mCreateJob->start(); + } +} + +void VacationCreateScriptJob::slotGenerateDone(const QString &error) +{ + mCreateJob = 0; + mUserJobRunning = false; + if (!error.isEmpty()) { + qWarning() << error; + mSuccess = false; + } + handleResult(); +} diff --git a/libksieve/ksieveui/vacation/vacationcreatescriptjob.h b/libksieve/ksieveui/vacation/vacationcreatescriptjob.h index c23584e78a..d5109c6e0a 100644 --- a/libksieve/ksieveui/vacation/vacationcreatescriptjob.h +++ b/libksieve/ksieveui/vacation/vacationcreatescriptjob.h @@ -1,65 +1,78 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #ifndef VACATIONCREATESCRIPTJOB_H #define VACATIONCREATESCRIPTJOB_H #include #include "ksieveui_export.h" #include namespace KManageSieve { class SieveJob; } namespace KSieveUi { +class ParseUserScriptJob; +class GenerateGlobalScriptJob; class KSIEVEUI_EXPORT VacationCreateScriptJob : public QObject { Q_OBJECT public: explicit VacationCreateScriptJob(QObject *parent=0); ~VacationCreateScriptJob(); void start(); + void kill(); void setServerUrl(const KUrl &url); void setScript(const QString &script); void setServerName(const QString &servername); + const QString &serverName() const; void setStatus(bool activate, bool wasActive); + void setKep14Support(bool kep14Support); Q_SIGNALS: void result(bool); void scriptActive(bool activated, const QString &serverName); private slots: - void slotPutActiveResult(KManageSieve::SieveJob *job, bool success); - void slotPutInactiveResult(KManageSieve::SieveJob *job, bool success); + void slotPutResult(KManageSieve::SieveJob *job, bool success); + void slotGetScript(KManageSieve::SieveJob *job, bool success, const QString &oldScript, bool active); + void slotGotActiveScripts(ParseUserScriptJob *job); + void slotGenerateDone(const QString &error=QString()); private: - void handlePutResult(KManageSieve::SieveJob *, bool success, bool activated); + void handleResult(); KUrl mUrl; QString mScript; QString mServerName; bool mActivate; - bool mWasActive; + bool mScriptActive; + bool mKep14Support; + bool mUserJobRunning; + bool mScriptJobRunning; + bool mSuccess; KManageSieve::SieveJob *mSieveJob; + ParseUserScriptJob *mParseUserJob; + GenerateGlobalScriptJob *mCreateJob; }; } #endif // VACATIONCREATESCRIPTJOB_H diff --git a/libksieve/ksieveui/vacation/vacationmanager.cpp b/libksieve/ksieveui/vacation/vacationmanager.cpp index 6cad984798..32c92eaa27 100644 --- a/libksieve/ksieveui/vacation/vacationmanager.cpp +++ b/libksieve/ksieveui/vacation/vacationmanager.cpp @@ -1,101 +1,103 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "vacationmanager.h" #include "ksieveui/vacation/multiimapvacationmanager.h" #include "ksieveui/vacation/multiimapvacationdialog.h" #include "ksieveui/vacation/vacationcreatescriptjob.h" #include #include #include using namespace KSieveUi; VacationManager::VacationManager(QWidget *parent) : QObject(parent), mWidget(parent) , mMultiImapVacationDialog(0) , mQuestionAsked(false) { mCheckVacation = new KSieveUi::MultiImapVacationManager( this ); connect( mCheckVacation, SIGNAL(scriptActive(bool,QString)), SIGNAL(updateVacationScriptStatus(bool,QString)) ); connect( mCheckVacation, SIGNAL(scriptActive(bool,QString)), SLOT(slotUpdateVacationScriptStatus(bool,QString)) ); } VacationManager::~VacationManager() { - delete mCheckVacation; + mCheckVacation = 0; + mMultiImapVacationDialog = 0; } void VacationManager::checkVacation() { mCheckVacation->checkVacation(); } void VacationManager::slotUpdateVacationScriptStatus(bool active, const QString &serverName) { if (active) { if (!mQuestionAsked) { mQuestionAsked = true; if ( KMessageBox::questionYesNo( 0, i18n( "There is still an active out-of-office reply configured.\n" "Do you want to edit it?"), i18n("Out-of-office reply still active"), KGuiItem( i18n( "Edit"), QLatin1String("document-properties") ), KGuiItem( i18n("Ignore"), QLatin1String("dialog-cancel") ) ) == KMessageBox::Yes ) { slotEditVacation(serverName); } } } } void VacationManager::slotEditVacation(const QString &serverName) { if ( mMultiImapVacationDialog ) { mMultiImapVacationDialog->raise(); mMultiImapVacationDialog->activateWindow(); } else { mMultiImapVacationDialog = new KSieveUi::MultiImapVacationDialog(mCheckVacation, mWidget); connect( mMultiImapVacationDialog, SIGNAL(okClicked()), SLOT(slotDialogOk()) ); connect( mMultiImapVacationDialog, SIGNAL(cancelClicked()), SLOT(slotDialogCanceled()) ); } mMultiImapVacationDialog->show(); if (!serverName.isEmpty()) { mMultiImapVacationDialog->switchToServerNamePage(serverName); } } void VacationManager::slotDialogCanceled() { mMultiImapVacationDialog->delayedDestruct(); mMultiImapVacationDialog = 0; } void VacationManager::slotDialogOk() { QList listJob = mMultiImapVacationDialog->listCreateJob(); Q_FOREACH (KSieveUi::VacationCreateScriptJob *job, listJob) { connect(job, SIGNAL(scriptActive(bool,QString)), SIGNAL(updateVacationScriptStatus(bool,QString))); + job->setKep14Support(mCheckVacation->kep14Support(job->serverName())); job->start(); } mMultiImapVacationDialog->delayedDestruct(); mMultiImapVacationDialog = 0; } diff --git a/libksieve/ksieveui/vacation/vacationpagewidget.cpp b/libksieve/ksieveui/vacation/vacationpagewidget.cpp index 784b9a4180..c26d0f3e7e 100644 --- a/libksieve/ksieveui/vacation/vacationpagewidget.cpp +++ b/libksieve/ksieveui/vacation/vacationpagewidget.cpp @@ -1,188 +1,191 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "vacationpagewidget.h" #include "vacationeditwidget.h" #include "vacationwarningwidget.h" #include "vacationcreatescriptjob.h" #include "vacationutils.h" #include "multiimapvacationmanager.h" #include #include "sieve-vacation.h" #include #include #include #include #include #include #include using namespace KSieveUi; VacationPageWidget::VacationPageWidget(QWidget *parent) : QWidget(parent), mPageScript(Script), mWasActive(false) { QVBoxLayout *lay = new QVBoxLayout; lay->setMargin(0); mStackWidget = new QStackedWidget; lay->addWidget(mStackWidget); //Main Page QWidget *mainPage = new QWidget; QVBoxLayout *vbox = new QVBoxLayout; mainPage->setLayout(vbox); mVacationWarningWidget = new VacationWarningWidget; vbox->addWidget(mVacationWarningWidget); mVacationEditWidget = new VacationEditWidget; vbox->addWidget(mVacationEditWidget); mStackWidget->addWidget(mainPage); QWidget *w = new QWidget; vbox = new QVBoxLayout; QLabel *lab = new QLabel(i18n( "Your server did not list \"vacation\" in " "its list of supported Sieve extensions;" "without it, KMail cannot install out-of-" "office replies for you." "Please contact your system administrator." ) ); vbox->addWidget(lab); vbox->setAlignment(lab, Qt::AlignVCenter); lab->setWordWrap(true); w->setLayout(vbox); mStackWidget->addWidget(w); mStackWidget->setCurrentIndex(Script); setLayout(lay); } VacationPageWidget::~VacationPageWidget() { } void VacationPageWidget::setServerUrl(const KUrl &url) { mUrl = url; mVacationEditWidget->setEnabled(false); } void VacationPageWidget::setVacationManager(MultiImapVacationManager *vacationManager) { mVacationManager = vacationManager; connect(mVacationManager, SIGNAL(scriptAvailable(QString,QStringList,QString,QString,bool)), SLOT(slotGetResult(QString,QStringList,QString,QString,bool))); mVacationManager->checkVacation(mServerName, mUrl); } void VacationPageWidget::setServerName(const QString &serverName) { mServerName = serverName; } void VacationPageWidget::slotGetResult(const QString &serverName, const QStringList &sieveCapabilities, const QString &scriptName, const QString &script, bool active) { if (serverName != mServerName) { return; } kDebug() << serverName << sieveCapabilities << endl << scriptName << "(" << active << ")" << endl << "script:" << endl << script; if ( mUrl.protocol() == QLatin1String("sieve") && !sieveCapabilities.contains(QLatin1String("vacation")) ) { mStackWidget->setCurrentIndex(ScriptNotSupported); return; } + mUrl.setFileName(scriptName); + // Whether the server supports the "date" extension const bool supportsSieveDate = mUrl.protocol() == QLatin1String("sieve") && sieveCapabilities.contains(QLatin1String("date")); QString messageText = VacationUtils::defaultMessageText(); QString subject = VacationUtils::defaultSubject(); int notificationInterval = VacationUtils::defaultNotificationInterval(); QStringList aliases = VacationUtils::defaultMailAliases(); bool sendForSpam = VacationUtils::defaultSendForSpam(); QString domainName = VacationUtils::defaultDomainName(); QDate startDate = VacationUtils::defaultStartDate(); QDate endDate = VacationUtils::defaultEndDate(); + bool scriptActive = true; - const bool bParse = KSieveUi::VacationUtils::parseScript(script, messageText, subject, notificationInterval, aliases, sendForSpam, domainName, startDate, endDate); + const bool bParse = KSieveUi::VacationUtils::parseScript(script, scriptActive, messageText, subject, notificationInterval, aliases, sendForSpam, domainName, startDate, endDate); if (!bParse) { mVacationWarningWidget->setVisible(true); } mWasActive = active; mVacationEditWidget->setEnabled(true); - mVacationEditWidget->setActivateVacation( active ); + mVacationEditWidget->setActivateVacation( active && scriptActive ); mVacationEditWidget->setMessageText( messageText ); mVacationEditWidget->setSubject( subject ); mVacationEditWidget->setNotificationInterval( notificationInterval ); mVacationEditWidget->setMailAliases( aliases.join(QLatin1String(", ")) ); mVacationEditWidget->setSendForSpam( sendForSpam ); mVacationEditWidget->setDomainName( domainName ); mVacationEditWidget->enableDomainAndSendForSpam( !VacationSettings::allowOutOfOfficeUploadButNoSettings() ); mVacationEditWidget->enableDates( supportsSieveDate ); if ( supportsSieveDate ) { mVacationEditWidget->setStartDate( startDate ); mVacationEditWidget->setEndDate( endDate ); } //emit scriptActive( mWasActive, mServerName ); } KSieveUi::VacationCreateScriptJob *VacationPageWidget::writeScript() { if (mPageScript == Script) { KSieveUi::VacationCreateScriptJob *createJob = new KSieveUi::VacationCreateScriptJob; createJob->setServerUrl(mUrl); createJob->setServerName(mServerName); - const QString script = VacationUtils::composeScript( mVacationEditWidget->messageText(), + const bool active = mVacationEditWidget->activateVacation(); + const QString script = VacationUtils::composeScript( mVacationEditWidget->messageText(), active, mVacationEditWidget->subject(), mVacationEditWidget->notificationInterval(), mVacationEditWidget->mailAliases(), mVacationEditWidget->sendForSpam(), mVacationEditWidget->domainName(), mVacationEditWidget->startDate(), mVacationEditWidget->endDate() ); - const bool active = mVacationEditWidget->activateVacation(); createJob->setStatus(active, mWasActive); //Q_EMIT scriptActive( active, mServerName); createJob->setScript(script); return createJob; } return 0; } void VacationPageWidget::setDefault() { if (mVacationEditWidget->isEnabled()) mVacationEditWidget->setDefault(); } diff --git a/libksieve/ksieveui/vacation/vacationscriptextractor.cpp b/libksieve/ksieveui/vacation/vacationscriptextractor.cpp index 42b5c6d53a..7e029d20f6 100644 --- a/libksieve/ksieveui/vacation/vacationscriptextractor.cpp +++ b/libksieve/ksieveui/vacation/vacationscriptextractor.cpp @@ -1,125 +1,228 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "vacationscriptextractor.h" using namespace KSieveUi; VacationDataExtractor::VacationDataExtractor() : KSieve::ScriptBuilder(), mContext( None ), mNotificationInterval( 0 ) + , mActive(true) + , mInIfBlock(false) + , mBlockLevel(0) + , mLineStart(0) + , mLineEnd(0) { kDebug(); } VacationDataExtractor::~VacationDataExtractor() { } -void VacationDataExtractor::commandStart( const QString & identifier ) { +void VacationDataExtractor::commandStart( const QString & identifier, int lineNumber ) { kDebug() << "( \"" << identifier <<"\" )"; + if (identifier == QLatin1String("if") && mContext == None) { + mContext = IfBlock; + mLineStart = lineNumber; + mInIfBlock = true; + } if ( identifier != QLatin1String("vacation") ) return; + + if (mContext != IfBlock) { + mLineStart = lineNumber; + } + reset(); mContext = VacationCommand; } -void VacationDataExtractor::commandEnd() { - kDebug(); - mContext = None; +void VacationDataExtractor::commandEnd(int lineNumber) { + if ( mContext != None && mContext != IfBlock && mContext != VacationEnd) { + mContext = VacationEnd; + mLineEnd = lineNumber; + } } void VacationDataExtractor::error( const KSieve::Error & e ) { kDebug() << e.asString() << "@" << e.line() << "," << e.column(); } void VacationDataExtractor::finished() { } +void VacationDataExtractor::testStart(const QString &test) +{ + if (mContext == IfBlock) { + if (test == QLatin1String("true") || test == QLatin1String("false")) { + mActive = (test == QLatin1String("true")); + mIfComment = QString(); + kDebug() << "set active level to" << mActive; + } + } +} + +void VacationDataExtractor::hashComment(const QString &comment) +{ + if (mContext == IfBlock) { + mIfComment += comment; + } +} + + +void VacationDataExtractor::blockStart(int lineNumber) +{ + mBlockLevel++; +} + +void VacationDataExtractor::blockEnd(int lineNumber) +{ + mBlockLevel--; + if(mBlockLevel == 0 && !commandFound()) { //We are in main level again, and didn't found vacation in block + mActive = true; + mIfComment = QString(); + } else if (mInIfBlock && mBlockLevel == 0 && commandFound()) { + mLineEnd = lineNumber; + mInIfBlock = false; + } +} + void VacationDataExtractor::taggedArgument( const QString & tag ) { kDebug() << "( \"" << tag <<"\" )"; if ( mContext != VacationCommand ) return; if ( tag == QLatin1String("days") ) mContext = Days; else if ( tag == QLatin1String("addresses") ) mContext = Addresses; else if (tag == QLatin1String("subject")) { mContext = Subject; } } void VacationDataExtractor::stringArgument( const QString & string, bool, const QString & ) { kDebug() << "( \"" << string <<"\" )"; if ( mContext == Addresses ) { mAliases.push_back( string ); mContext = VacationCommand; } else if (mContext == Subject) { mSubject = string; mContext = VacationCommand; } else if ( mContext == VacationCommand ) { mMessageText = string; mContext = VacationCommand; } } void VacationDataExtractor::numberArgument( unsigned long number, char ) { kDebug() << "( \"" << number <<"\" )"; if ( mContext != Days ) return; if ( number > INT_MAX ) mNotificationInterval = INT_MAX; else mNotificationInterval = number; mContext = VacationCommand; } void VacationDataExtractor::stringListArgumentStart() { } void VacationDataExtractor::stringListEntry( const QString & string, bool, const QString & ) { kDebug() << "( \"" << string <<"\" )"; if ( mContext != Addresses ) return; mAliases.push_back( string ); } void VacationDataExtractor::stringListArgumentEnd() { kDebug(); if ( mContext != Addresses ) return; mContext = VacationCommand; } void VacationDataExtractor::reset() { kDebug(); mContext = None; mNotificationInterval = 0; mAliases.clear(); mMessageText.clear(); } + +RequireExtractor::RequireExtractor() + : KSieve::ScriptBuilder() + , mContext( None ) + , mLineStart(0) + , mLineEnd(0) +{ + +} + +RequireExtractor::~RequireExtractor() +{ + +} + +void RequireExtractor::commandStart(const QString &identifier, int lineNumber) +{ + if (identifier == QLatin1String("require") && mContext == None) { + mContext = RequireCommand; + mLineStart = lineNumber; + } +} + +void RequireExtractor::commandEnd(int lineNumber) +{ + if (mContext == RequireCommand) { + mContext = EndState; + mLineEnd = lineNumber; + } +} + +void RequireExtractor::error(const KSieve::Error &e) +{ + kDebug() << e.asString() << "@" << e.line() << "," << e.column(); +} + +void RequireExtractor::finished() +{ + +} + +void RequireExtractor::stringArgument(const QString &string, bool, const QString &) +{ + mRequirements << string; +} + +void RequireExtractor::stringListEntry(const QString &string, bool, const QString &) +{ + mRequirements << string; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/vacationscriptextractor.h b/libksieve/ksieveui/vacation/vacationscriptextractor.h index fc03e058a1..1812ada37d 100644 --- a/libksieve/ksieveui/vacation/vacationscriptextractor.h +++ b/libksieve/ksieveui/vacation/vacationscriptextractor.h @@ -1,445 +1,518 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #ifndef VACATIONSCRIPTEXTRACTOR_H #define VACATIONSCRIPTEXTRACTOR_H #include "sieve-vacation.h" #include "util/util.h" #include #include #include #include #include #include #include #include #include namespace KSieveExt { class MultiScriptBuilder : public KSieve::ScriptBuilder { std::vector mBuilders; public: MultiScriptBuilder() : KSieve::ScriptBuilder() {} MultiScriptBuilder( KSieve::ScriptBuilder * sb1 ) : KSieve::ScriptBuilder(), mBuilders( 1 ) { mBuilders[0] = sb1; assert( sb1 ); } MultiScriptBuilder( KSieve::ScriptBuilder * sb1, KSieve::ScriptBuilder * sb2 ) : KSieve::ScriptBuilder(), mBuilders( 2 ) { mBuilders[0] = sb1; mBuilders[1] = sb2; assert( sb1 ); assert( sb2 ); } MultiScriptBuilder( KSieve::ScriptBuilder * sb1, KSieve::ScriptBuilder * sb2, KSieve::ScriptBuilder * sb3 ) : KSieve::ScriptBuilder(), mBuilders( 3 ) { mBuilders[0] = sb1; mBuilders[1] = sb2; mBuilders[2] = sb3; assert( sb1 ); assert( sb2 ); assert( sb3 ); } MultiScriptBuilder( KSieve::ScriptBuilder * sb1, KSieve::ScriptBuilder * sb2, KSieve::ScriptBuilder * sb3, KSieve::ScriptBuilder * sb4 ) : KSieve::ScriptBuilder(), mBuilders( 4 ) { mBuilders[0] = sb1; mBuilders[1] = sb2; mBuilders[2] = sb3; mBuilders[3] = sb4; assert( sb1 ); assert( sb2 ); assert( sb3 ); assert( sb4 ); } ~MultiScriptBuilder() {} private: #ifdef FOREACH #undef FOREACH #endif #define FOREACH for ( std::vector::const_iterator it = mBuilders.begin(), end = mBuilders.end() ; it != end ; ++it ) (*it)-> - void commandStart( const QString & identifier ) { FOREACH commandStart( identifier ); } - void commandEnd() { FOREACH commandEnd(); } + void commandStart( const QString & identifier, int lineNumber ) { FOREACH commandStart( identifier, lineNumber ); } + void commandEnd(int lineNumber) { FOREACH commandEnd(lineNumber); } void testStart( const QString & identifier ) { FOREACH testStart( identifier ); } void testEnd() { FOREACH testEnd(); } void testListStart() { FOREACH testListStart(); } void testListEnd() { FOREACH testListEnd(); } - void blockStart() { FOREACH blockStart(); } - void blockEnd() { FOREACH blockEnd(); } + void blockStart(int lineNumber) { FOREACH blockStart(lineNumber); } + void blockEnd(int lineNumber) { FOREACH blockEnd(lineNumber); } void hashComment( const QString & comment ) { FOREACH hashComment( comment ); } void bracketComment( const QString & comment ) { FOREACH bracketComment( comment ); } void lineFeed() { FOREACH lineFeed(); } void error( const KSieve::Error & e ) { FOREACH error( e ); } void finished() { FOREACH finished(); } void taggedArgument( const QString & tag ) { FOREACH taggedArgument( tag ); } void stringArgument( const QString & string, bool multiline, const QString & fixme ) { FOREACH stringArgument( string, multiline, fixme ); } void numberArgument( unsigned long number, char quantifier ) { FOREACH numberArgument( number, quantifier ); } void stringListArgumentStart() { FOREACH stringListArgumentStart(); } void stringListEntry( const QString & string, bool multiline, const QString & fixme) { FOREACH stringListEntry( string, multiline, fixme ); } void stringListArgumentEnd() { FOREACH stringListArgumentEnd(); } #undef FOREACH }; } namespace KSieveUi { class GenericInformationExtractor : public KSieve::ScriptBuilder { public: enum BuilderMethod { Any, TaggedArgument, StringArgument, NumberArgument, CommandStart, CommandEnd, TestStart, TestEnd, TestListStart, TestListEnd, BlockStart, BlockEnd, StringListArgumentStart, StringListEntry, StringListArgumentEnd }; struct StateNode { // expectation: int depth; BuilderMethod method; const char * string; // actions: int if_found; int if_not_found; const char * save_tag; }; const std::vector mNodes; std::map mResults; std::set mRecursionGuard; unsigned int mState; int mNestingDepth; + int mLineNumber; + public: GenericInformationExtractor( const std::vector & nodes ) - : KSieve::ScriptBuilder(), mNodes( nodes ), mState( 0 ), mNestingDepth( 0 ) {} + : KSieve::ScriptBuilder(), mNodes( nodes ), mState( 0 ), mNestingDepth( 0 ), mLineNumber(0) {} const std::map & results() const { return mResults; } private: virtual void process( BuilderMethod method, const QString & string=QString() ) { doProcess( method, string ); mRecursionGuard.clear(); } void doProcess( BuilderMethod method, const QString & string ) { mRecursionGuard.insert( mState ); bool found = true; const StateNode & expected = mNodes[mState]; if ( expected.depth != -1 && mNestingDepth != expected.depth ) found = false; if ( expected.method != Any && method != expected.method ) found = false; if ( const char * str = expected.string ) if ( string.toLower() != QString::fromUtf8( str ).toLower() ) found = false; kDebug() << ( found ?"found:" :"not found:" ) << mState << "->" << ( found ? expected.if_found : expected.if_not_found ); mState = found ? expected.if_found : expected.if_not_found ; assert( mState < mNodes.size() ); if ( found ) if ( const char * save_tag = expected.save_tag ) { kDebug() << "stored tag" << save_tag << ":" << string; mResults[QString::fromLatin1(save_tag)] = string; } if ( !found && !mRecursionGuard.count( mState ) ) { doProcess( method, string ); } } - void commandStart( const QString & identifier ) { kDebug() ; process( CommandStart, identifier ); } - void commandEnd() { kDebug() ; process( CommandEnd ); } - void testStart( const QString & identifier ) { kDebug() ; process( TestStart, identifier ); } + void commandStart( const QString & identifier, int lineNumber ) { kDebug() << identifier ; process( CommandStart, identifier ); } + void commandEnd(int lineNumber) { kDebug() ; process( CommandEnd ); } + void testStart( const QString & identifier ) { kDebug() << identifier ; process( TestStart, identifier ); } void testEnd() { kDebug() ; process( TestEnd ); } void testListStart() { kDebug() ; process( TestListStart ); } void testListEnd() { kDebug() ; process( TestListEnd ); } - void blockStart() { kDebug() ; process( BlockStart ); ++mNestingDepth; } - void blockEnd() { kDebug() ; --mNestingDepth; process( BlockEnd ); } + void blockStart(int lineNumber) { kDebug() ; process( BlockStart ); ++mNestingDepth; } + void blockEnd(int lineNumber) { kDebug() ; --mNestingDepth; process( BlockEnd ); } void hashComment( const QString & ) { kDebug() ; } void bracketComment( const QString & ) { kDebug() ; } - void lineFeed() { kDebug() ; } + void lineFeed() { kDebug() << ++mLineNumber; } void error( const KSieve::Error & ) { kDebug() ; mState = 0; } void finished() { kDebug() ; } void taggedArgument( const QString & tag ) { kDebug() ; process( TaggedArgument, tag ); } void stringArgument( const QString & string, bool, const QString & ) { kDebug() ; process( StringArgument, string ); } void numberArgument( unsigned long number, char ) { kDebug(); process( NumberArgument, QString::number( number ) ); } void stringListArgumentStart() { kDebug() ; process( StringListArgumentStart ); } void stringListEntry( const QString & string, bool, const QString & ) { kDebug() ; process( StringListEntry, string ); } void stringListArgumentEnd() { kDebug() ; process( StringListArgumentEnd ); } }; typedef GenericInformationExtractor GIE; static const GenericInformationExtractor::StateNode spamNodes[] = { { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0 - { 0, GIE::TestStart, "header", 2, 0, 0 }, // 1 - { 0, GIE::TaggedArgument, "contains", 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "allof", 2, 3, 0 }, // 1 + { 0, GIE::TestListStart, 0, 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "not", 4, 3, 0 }, // 3 + { 0, GIE::TestStart, "header", 5, 3, 0 }, // 4 + { 0, GIE::TaggedArgument, "contains", 6, 0, 0 }, // 5 // accept both string and string-list: - { 0, GIE::StringArgument, "x-spam-flag", 9, 4, "x-spam-flag" }, // 3 - { 0, GIE::StringListArgumentStart, 0, 5, 0, 0 }, // 4 - { 0, GIE::StringListEntry, "x-spam-flag", 6, 7, "x-spam-flag" }, // 5 - { 0, GIE::StringListEntry, 0, 6, 8, 0 }, // 6 - { 0, GIE::StringListArgumentEnd, 0, 0, 5, 0 }, // 7 - { 0, GIE::StringListArgumentEnd, 0, 9, 0, 0 }, // 8 + { 0, GIE::StringArgument, "x-spam-flag", 12, 7, "x-spam-flag" }, // 6 + { 0, GIE::StringListArgumentStart, 0, 8, 0, 0 }, // 7 + { 0, GIE::StringListEntry, "x-spam-flag", 9, 10, "x-spam-flag" }, // 8 + { 0, GIE::StringListEntry, 0, 9, 11, 0 }, // 9 + { 0, GIE::StringListArgumentEnd, 0, 0, 8, 0 }, // 10 + { 0, GIE::StringListArgumentEnd, 0, 12, 0, 0 }, // 11 // accept both string and string-list: - { 0, GIE::StringArgument, "yes", 15, 10, "spam-flag-yes" }, // 9 - { 0, GIE::StringListArgumentStart, 0, 11, 0, 0 }, // 10 - { 0, GIE::StringListEntry, "yes", 12, 13, "spam-flag-yes" }, // 11 - { 0, GIE::StringListEntry, 0, 12, 14, 0 }, // 12 - { 0, GIE::StringListArgumentEnd, 0, 0, 11, 0 }, // 13 - { 0, GIE::StringListArgumentEnd, 0, 15, 0, 0 }, // 14 + { 0, GIE::StringArgument, "yes", 18, 13, "spam-flag-yes" }, // 12 + { 0, GIE::StringListArgumentStart, 0, 14, 0, 0 }, // 13 + { 0, GIE::StringListEntry, "yes", 15, 16, "spam-flag-yes" }, // 14 + { 0, GIE::StringListEntry, 0, 15, 17, 0 }, // 15 + { 0, GIE::StringListArgumentEnd, 0, 0, 14, 0 }, // 16 + { 0, GIE::StringListArgumentEnd, 0, 18, 0, 0 }, // 17 - { 0, GIE::TestEnd, 0, 16, 0, 0 }, // 15 + { 0, GIE::TestEnd, 0, 21, 20, 0 }, // 18 + { 0, GIE::Any, 0, 21, 0, 0 }, // 19 + { 0, GIE::TestListEnd, 0, 21, 19, 0 }, // 20 // block of command, find "stop", take nested if's into account: - { 0, GIE::BlockStart, 0, 17, 0, 0 }, // 16 - { 1, GIE::CommandStart, "stop", 20, 19, "stop" }, // 17 - { -1, GIE::Any, 0, 17, 0, 0 }, // 18 - { 0, GIE::BlockEnd, 0, 0, 18, 0 }, // 19 + { 0, GIE::BlockStart, 0, 22, 18, 0 }, // 21 + { 1, GIE::CommandStart, "vacation", 24, 22, "vacation" }, // 22 + { 1, GIE::Any, 0, 24, 0, 0 }, // 23 + { 0, GIE::BlockEnd, 0, 25, 23, 0 }, // 24 - { -1, GIE::Any, 0, 20, 20, 0 }, // 20 end state + { -1, GIE::Any, 0, 25, 25, 0 }, // 25 end state }; static const unsigned int numSpamNodes = sizeof spamNodes / sizeof *spamNodes ; class SpamDataExtractor : public GenericInformationExtractor { public: SpamDataExtractor() : GenericInformationExtractor( std::vector( spamNodes, spamNodes + numSpamNodes ) ) { } bool found() const { return mResults.count( QLatin1String("x-spam-flag") ) && mResults.count( QLatin1String("spam-flag-yes") ) && - mResults.count( QLatin1String("stop") ) ; + mResults.count( QLatin1String("vacation") ) ; } }; // to understand this table, study the output of // libksieve/tests/parsertest // 'if not address :domain :contains ["from"] ["mydomain.org"] { keep; stop; }' static const GenericInformationExtractor::StateNode domainNodes[] = { { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0 - { 0, GIE::TestStart, "not", 2, 0, 0, }, // 1 - { 0, GIE::TestStart, "address", 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "allof", 2, 3, 0 }, // 1 + { 0, GIE::TestListStart, 0, 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "address", 4, 3, 0 }, // 3 // :domain and :contains in arbitrary order: - { 0, GIE::TaggedArgument, "domain", 4, 5, 0 }, // 3 - { 0, GIE::TaggedArgument, "contains", 7, 0, 0 }, // 4 - { 0, GIE::TaggedArgument, "contains", 6, 0, 0 }, // 5 - { 0, GIE::TaggedArgument, "domain", 7, 0, 0 }, // 6 + { 0, GIE::TaggedArgument, "domain", 5, 6, 0 }, // 4 + { 0, GIE::TaggedArgument, "contains", 8, 0, 0 }, // 5 + { 0, GIE::TaggedArgument, "contains", 7, 0, 0 }, // 6 + { 0, GIE::TaggedArgument, "domain", 8, 0, 0 }, // 7 // accept both string and string-list: - { 0, GIE::StringArgument, "from", 13, 8, "from" }, // 7 - { 0, GIE::StringListArgumentStart, 0, 9, 0, 0 }, // 8 - { 0, GIE::StringListEntry, "from", 10, 11, "from" }, // 9 - { 0, GIE::StringListEntry, 0, 10, 12, 0 }, // 10 - { 0, GIE::StringListArgumentEnd, 0, 0, 9, 0 }, // 11 - { 0, GIE::StringListArgumentEnd, 0, 13, 0, 0 }, // 12 + { 0, GIE::StringArgument, "from", 14, 9, "from" }, // 8 + { 0, GIE::StringListArgumentStart, 0, 10, 0, 0 }, // 9 + { 0, GIE::StringListEntry, "from", 11, 12, "from" }, // 10 + { 0, GIE::StringListEntry, 0, 11, 13, 0 }, // 11 + { 0, GIE::StringListArgumentEnd, 0, 0, 10, 0 }, // 12 + { 0, GIE::StringListArgumentEnd, 0, 14, 0, 0 }, // 13 // string: save, string-list: save last - { 0, GIE::StringArgument, 0, 17, 14, "domainName" }, // 13 - { 0, GIE::StringListArgumentStart, 0, 15, 0, 0 }, // 14 - { 0, GIE::StringListEntry, 0, 15, 16, "domainName" }, // 15 - { 0, GIE::StringListArgumentEnd, 0, 17, 0, 0 }, // 16 + { 0, GIE::StringArgument, 0, 18, 15, "domainName" }, // 14 + { 0, GIE::StringListArgumentStart, 0, 16, 0, 0 }, // 15 + { 0, GIE::StringListEntry, 0, 16, 17, "domainName" }, // 16 + { 0, GIE::StringListArgumentEnd, 0, 18, 0, 0 }, // 17 - { 0, GIE::TestEnd, 0, 18, 0, 0 }, // 17 - { 0, GIE::TestEnd, 0, 19, 0, 0 }, // 18 + { 0, GIE::TestEnd, 0, 18, 20, 0 }, // 18 + { 0, GIE::Any, 0, 18, 0, 0 }, // 19 // block of commands, find "stop", take nested if's into account: - { 0, GIE::BlockStart, 0, 20, 0, 0 }, // 19 - { 1, GIE::CommandStart, "stop", 23, 22, "stop" }, // 20 - { -1, GIE::Any, 0, 20, 0, 0 }, // 21 - { 0, GIE::BlockEnd, 0, 0, 21, 0 }, // 22 + { 0, GIE::BlockStart, 0, 21, 19, 0 }, // 20 + { 1, GIE::CommandStart, "vacation", 23, 21, "vacation" }, // 21 + { 1, GIE::Any, 0, 23, 0, 0 }, // 22 + { 0, GIE::BlockEnd, 0, 24, 22, 0 }, // 23 - { -1, GIE::Any, 0, 23, 23, 0 } // 23 end state + { -1, GIE::Any, 0, 24, 24, 0 } // 24 end state }; static const unsigned int numDomainNodes = sizeof domainNodes / sizeof *domainNodes ; class DomainRestrictionDataExtractor : public GenericInformationExtractor { public: DomainRestrictionDataExtractor() : GenericInformationExtractor( std::vector( domainNodes, domainNodes+numDomainNodes ) ) { } QString domainName() /*not const, since map::op[] isn't const*/ { - return mResults.count( QLatin1String("stop") ) && mResults.count( QLatin1String("from") ) + return mResults.count( QLatin1String("vacation") ) && mResults.count( QLatin1String("from") ) ? mResults[QLatin1String("domainName")] : QString(); } }; // if not allof (currentdate :value "ge" date "YYYY-MM-DD", // currentfate :value "le" date "YYYY-MM-DD) { keep; stop; } static const GenericInformationExtractor::StateNode datesNodes[] = { { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0 - { 0, GIE::TestStart, "not", 2, 0, 0 }, // 1 - { 0, GIE::TestStart, "allof", 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "allof", 2, 0, 0 }, // 1 // handle startDate and endDate in arbitrary order - { 0, GIE::TestListStart, 0, 4, 0, 0 }, // 3 - { 0, GIE::TestStart, "currentdate", 5, 0, 0 }, // 4 - { 0, GIE::TaggedArgument, "value", 6, 0, 0 }, // 5 - { 0, GIE::StringArgument, "ge", 7, 9, 0 }, // 6 - { 0, GIE::StringArgument, "date", 8, 0, 0 }, // 7 - { 0, GIE::StringArgument, 0, 12, 0, "startDate" }, // 8 - { 0, GIE::StringArgument, "le", 10, 0, 0 }, // 9 - { 0, GIE::StringArgument, "date", 11, 0, 0 }, // 10 - { 0, GIE::StringArgument, 0, 12, 0, "endDate" }, // 11 - { 0, GIE::TestEnd, 0, 13, 0, 0 }, // 12 - - { 0, GIE::TestStart, "currentdate", 14, 0, 0 }, // 13 - { 0, GIE::TaggedArgument, "value", 15, 0, 0 }, // 14 - { 0, GIE::StringArgument, "le", 16, 18, 0 }, // 15 - { 0, GIE::StringArgument, "date", 17, 0, 0 }, // 16 - { 0, GIE::StringArgument, 0, 21, 0, "endDate" }, // 17 - { 0, GIE::StringArgument, "ge", 19, 0, 0 }, // 18 - { 0, GIE::StringArgument, "date", 20, 0, 0 }, // 19 - { 0, GIE::StringArgument, 0, 21, 0, "startDate" }, // 20 - { 0, GIE::TestEnd, 0, 22, 0, 0 }, // 21 - { 0, GIE::TestListEnd, 0, 23, 0, 0 }, // 22 - - { 0, GIE::TestEnd, 0, 24, 0, 0 }, // 23 - { 0, GIE::TestEnd, 0, 25, 0, 0 }, // 24 + { 0, GIE::TestListStart, 0, 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "currentdate", 4, 3, 0 }, // 3 + { 0, GIE::TaggedArgument, "value", 5, 4, 0 }, // 4 + { 0, GIE::StringArgument, "ge", 6, 8, 0 }, // 5 + { 0, GIE::StringArgument, "date", 7, 0, 0 }, // 6 + { 0, GIE::StringArgument, 0, 11, 0, "startDate" }, // 7 + { 0, GIE::StringArgument, "le", 9, 0, 0 }, // 8 + { 0, GIE::StringArgument, "date", 10, 0, 0 }, // 9 + { 0, GIE::StringArgument, 0, 11, 0, "endDate" }, // 10 + { 0, GIE::TestEnd, 0, 12, 0, 0 }, // 11 + + { 0, GIE::TestStart, "currentdate", 13, 12, 0 }, // 12 + { 0, GIE::TaggedArgument, "value", 14, 13, 0 }, // 13 + { 0, GIE::StringArgument, "le", 15, 17, 0 }, // 14 + { 0, GIE::StringArgument, "date", 16, 0, 0 }, // 15 + { 0, GIE::StringArgument, 0, 20, 0, "endDate" }, // 16 + { 0, GIE::StringArgument, "ge", 18, 0, 0 }, // 17 + { 0, GIE::StringArgument, "date", 19, 0, 0 }, // 18 + { 0, GIE::StringArgument, 0, 20, 0, "startDate" }, // 19 + { 0, GIE::TestEnd, 0, 24, 0, 0 }, // 20 + { 0, GIE::TestStart, 0, 23, 22, 0 }, // 21 + { -1, GIE::Any, 0, 24, 0, 0 }, // 22 + { 0, GIE::TestEnd, 0, 24, 22, 0 }, // 23 + { 0, GIE::TestListEnd, 0, 25, 21, 0 }, // 24 + + { 0, GIE::TestEnd, 0, 26, 0, 0 }, // 25 // block of commands, find "stop", take nested if's into account: - { 0, GIE::BlockStart, 0, 26, 0, 0 }, // 25 - { 1, GIE::CommandStart, "stop", 29, 28, "stop" }, // 26 - { -1, GIE::Any, 0, 26, 0, 0 }, // 27 - { 0, GIE::BlockEnd, 0, 0, 27, 0 }, // 28 + { 0, GIE::BlockStart, 0, 28, 25, 0 }, // 26 + { -1, GIE::Any, 0, 28, 0, 0 }, // 27 + { 1, GIE::CommandStart, "vacation", 30, 27, "vacation" }, // 28 + { -1, GIE::Any, 0, 30, 0, 0 }, // 29 + { 0, GIE::BlockEnd, 0, 31, 29, 0 }, // 30 - { -1, GIE::Any, 0, 27, 27, 0 } // 29 end state + { -1, GIE::Any, 0, 31, 31, 0 } // 31 end state }; static const unsigned int numDatesNodes = sizeof datesNodes / sizeof *datesNodes; class DateExtractor : public GenericInformationExtractor { public: DateExtractor() : GenericInformationExtractor( std::vector( datesNodes, datesNodes+numDatesNodes ) ) { } QDate endDate() { return date( QLatin1String( "endDate" ) ); } QDate startDate() { return date( QLatin1String( "startDate" ) ); } private: QDate date(const QString &name ) { if (mResults.count( name ) == 0) { return QDate(); } else { return QDate::fromString( mResults[name], Qt::ISODate ); } } }; class VacationDataExtractor : public KSieve::ScriptBuilder { enum Context { None = 0, // command itself: VacationCommand, // tagged args: - Days, Addresses, Subject + Days, Addresses, Subject, + VacationEnd, + IfBlock }; public: VacationDataExtractor(); virtual ~VacationDataExtractor(); + bool commandFound() const { return mContext == VacationEnd; } + bool active() const { return mActive; } int notificationInterval() const { return mNotificationInterval; } const QString & messageText() const { return mMessageText; } const QStringList & aliases() const { return mAliases; } + const QString &ifComment() const { return mIfComment; } const QString &subject() const { return mSubject; } + int lineStart() const {return mLineStart;} + int lineEnd() const {return mLineEnd;} + private: - void commandStart( const QString & identifier ); + void commandStart( const QString & identifier, int lineNumber ); - void commandEnd(); + void commandEnd(int lineNumber); - void testStart( const QString & ) {} + void testStart( const QString &); void testEnd() {} void testListStart() {} void testListEnd() {} - void blockStart() {} - void blockEnd() {} - void hashComment( const QString & ) {} - void bracketComment( const QString & ) {} + void blockStart(int lineNumber); + void blockEnd(int lineNumber); + void hashComment( const QString & ); + void bracketComment( const QString &c ) {} void lineFeed() {} void error( const KSieve::Error & e ); void finished(); void taggedArgument( const QString & tag ); void stringArgument( const QString & string, bool, const QString & ); void numberArgument( unsigned long number, char ); void stringListArgumentStart(); void stringListEntry( const QString & string, bool, const QString & ); void stringListArgumentEnd(); private: Context mContext; int mNotificationInterval; QString mMessageText; QString mSubject; QStringList mAliases; + bool mActive; + bool mInIfBlock; + int mBlockLevel; + QString mIfComment; + int mLineStart; + int mLineEnd; void reset(); }; +class RequireExtractor : public KSieve::ScriptBuilder { + enum Context { + None = 0, + // command itself: + RequireCommand, + EndState + }; +public: + RequireExtractor(); + virtual ~RequireExtractor(); + + bool commandFound() const { return mContext == EndState; } + const QStringList &requirements() const { return mRequirements; } + + int lineStart() const {return mLineStart;} + int lineEnd() const {return mLineEnd;} + +private: + void commandStart( const QString & identifier, int lineNumber ); + + void commandEnd(int lineNumber); + + void testStart( const QString &) {} + void testEnd() {} + void testListStart() {} + void testListEnd() {} + void blockStart(int lineNumber){}; + void blockEnd(int lineNumber){}; + void hashComment( const QString & ) {} + void bracketComment( const QString & ) {} + void lineFeed() {} + void error( const KSieve::Error & e ); + void finished(); + + void taggedArgument( const QString & tag ) {} + void numberArgument( unsigned long number, char ) {} + + void stringArgument( const QString & string, bool, const QString & ); + + void stringListArgumentStart(){} + void stringListEntry( const QString & string, bool, const QString & ); + void stringListArgumentEnd(){} + +private: + Context mContext; + QStringList mRequirements; + int mLineStart; + int mLineEnd; +}; } #endif // VACATIONSCRIPTEXTRACTOR_H diff --git a/libksieve/ksieveui/vacation/vacationutils.cpp b/libksieve/ksieveui/vacation/vacationutils.cpp index 0604e127cb..806600d76f 100644 --- a/libksieve/ksieveui/vacation/vacationutils.cpp +++ b/libksieve/ksieveui/vacation/vacationutils.cpp @@ -1,221 +1,422 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #include "vacationutils.h" #include "vacationscriptextractor.h" #include "sieve-vacation.h" #include #include #include #include #include #include using KMime::Types::AddrSpecList; static inline QString dotstuff( QString s ) { // krazy:exclude=passbyvalue if ( s.startsWith( QLatin1Char('.') ) ) return QLatin1Char('.') + s.replace( QLatin1String("\n."), QLatin1String("\n..") ); else return s.replace( QLatin1String("\n."), QLatin1String("\n..") ); } static inline QString stringReplace(QString s) { s = s.replace(QRegExp(QLatin1String("[\n\t]+")),QLatin1String(" ")); return s.replace(QLatin1Char('\"'),QLatin1String("\\\"")); } QString KSieveUi::VacationUtils::defaultSubject() { return i18n("Out of office till %1", QLocale().toString(QDate::currentDate().addDays(1))); } QString KSieveUi::VacationUtils::defaultMessageText() { return i18n( "I am out of office till %1.\n" "\n" "In urgent cases, please contact Mrs. \"vacation replacement\"\n" "\n" "email: \"email address of vacation replacement\"\n" "phone: +49 711 1111 11\n" "fax.: +49 711 1111 12\n" "\n" "Yours sincerely,\n" "-- \"enter your name and email address here\"\n", KGlobal::locale()->formatDate( QDate::currentDate().addDays( 1 ) ) ); } int KSieveUi::VacationUtils::defaultNotificationInterval() { return 7; // days } QStringList KSieveUi::VacationUtils::defaultMailAliases() { QStringList sl; KPIMIdentities::IdentityManager manager( true ); KPIMIdentities::IdentityManager::ConstIterator end(manager.end()); for ( KPIMIdentities::IdentityManager::ConstIterator it = manager.begin(); it != end ; ++it ) { if ( !(*it).primaryEmailAddress().isEmpty() ) { sl.push_back( (*it).primaryEmailAddress() ); } sl += (*it).emailAliases(); } return sl; } bool KSieveUi::VacationUtils::defaultSendForSpam() { return VacationSettings::outOfOfficeReactToSpam(); } QString KSieveUi::VacationUtils::defaultDomainName() { return VacationSettings::outOfOfficeDomain(); } QDate KSieveUi::VacationUtils::defaultStartDate() { return QDate::currentDate(); } QDate KSieveUi::VacationUtils::defaultEndDate() { return defaultStartDate().addDays(7); } -bool KSieveUi::VacationUtils::parseScript( const QString &script, QString &messageText, +bool KSieveUi::VacationUtils::parseScript( const QString &script, bool &active, QString &messageText, QString &subject, int & notificationInterval, QStringList &aliases, bool & sendForSpam, QString &domainName, QDate & startDate, QDate & endDate ) { if ( script.trimmed().isEmpty() ) { + active = false; messageText = VacationUtils::defaultMessageText(); subject = VacationUtils::defaultSubject(); notificationInterval = VacationUtils::defaultNotificationInterval(); aliases = VacationUtils::defaultMailAliases(); sendForSpam = VacationUtils::defaultSendForSpam(); domainName = VacationUtils::defaultDomainName(); return true; } // The trimmed() call below prevents parsing errors. The // slave somehow omits the last \n, which results in a lone \r at // the end, leading to a parse error. const QByteArray scriptUTF8 = script.trimmed().toUtf8(); kDebug() << "scriptUtf8 = \"" + scriptUTF8 +"\""; KSieve::Parser parser( scriptUTF8.begin(), scriptUTF8.begin() + scriptUTF8.length() ); VacationDataExtractor vdx; SpamDataExtractor sdx; DomainRestrictionDataExtractor drdx; DateExtractor dx; - KSieveExt::MultiScriptBuilder tsb( &vdx, &sdx, &drdx, &dx ); + KSieveExt::MultiScriptBuilder tsb( &vdx , &sdx, &drdx, &dx ); parser.setScriptBuilder( &tsb ); - if ( !parser.parse() ) + parser.parse(); + if ( !parser.parse() || !vdx.commandFound() ) { + active = false; return false; + } + active = vdx.active(); messageText = vdx.messageText().trimmed(); if (!vdx.subject().isEmpty()) { subject = vdx.subject().trimmed(); } notificationInterval = vdx.notificationInterval(); aliases = vdx.aliases(); - if ( !VacationSettings::allowOutOfOfficeUploadButNoSettings() ) { - sendForSpam = !sdx.found(); - domainName = drdx.domainName(); + + if (!active && !vdx.ifComment().isEmpty()) { + const QByteArray newScript = QString::fromAscii("if ").toUtf8() + vdx.ifComment().toUtf8() + QString::fromLatin1("{vacation;}").toUtf8(); + tsb = KSieveExt::MultiScriptBuilder( &sdx, &drdx, &dx ); + KSieve::Parser parser( newScript.begin(), + newScript.begin() + newScript.length() ); + parser.setScriptBuilder( &tsb ); + if ( !parser.parse() ) { + return false; + } } + + sendForSpam = !sdx.found(); + domainName = drdx.domainName(); startDate = dx.startDate(); endDate = dx.endDate(); return true; } bool KSieveUi::VacationUtils::foundVacationScript(const QString &script) { const QByteArray scriptUTF8 = script.trimmed().toUtf8(); kDebug() << "scriptUtf8 = \"" + scriptUTF8 +"\""; if (scriptUTF8.isEmpty()) { return false; } KSieve::Parser parser( scriptUTF8.begin(), scriptUTF8.begin() + scriptUTF8.length() ); VacationDataExtractor vdx; - SpamDataExtractor sdx; - DomainRestrictionDataExtractor drdx; - DateExtractor dx; - KSieveExt::MultiScriptBuilder tsb( &vdx, &sdx, &drdx, &dx ); - parser.setScriptBuilder( &tsb ); - return parser.parse(); + parser.setScriptBuilder(&vdx); + return parser.parse() && vdx.commandFound(); } -QString KSieveUi::VacationUtils::composeScript( const QString & messageText, +QString composeOldScript( const QString & messageText, const QString &subject, int notificationInterval, const AddrSpecList & addrSpecs, bool sendForSpam, const QString & domain, const QDate & startDate, const QDate & endDate ) { QString addressesArgument; QStringList aliases; if ( !addrSpecs.empty() ) { addressesArgument += QLatin1String(":addresses [ "); QStringList sl; AddrSpecList::const_iterator end = addrSpecs.constEnd(); for ( AddrSpecList::const_iterator it = addrSpecs.begin() ; it != end; ++it ) { sl.push_back( QLatin1Char('"') + (*it).asString().replace( QLatin1Char('\\'), QLatin1String("\\\\") ).replace( QLatin1Char('"'), QLatin1String("\\\"") ) + QLatin1Char('"') ); aliases.push_back( (*it).asString() ); } addressesArgument += sl.join( QLatin1String(", ") ) + QLatin1String(" ] "); } QString script; if ( startDate.isValid() && endDate.isValid() ) { script = QString::fromLatin1("require [\"vacation\", \"relational\", \"date\"];\n\n" ); } else { script = QString::fromLatin1("require \"vacation\";\n\n" ); } if ( !sendForSpam ) script += QString::fromLatin1( "if header :contains \"X-Spam-Flag\" \"YES\"" " { keep; stop; }\n" ); // FIXME? if ( !domain.isEmpty() ) // FIXME script += QString::fromLatin1( "if not address :domain :contains \"from\" \"%1\" { keep; stop; }\n" ).arg( domain ); if ( startDate.isValid() && endDate.isValid() ) { script += QString::fromLatin1( "if not allof(currentdate :value \"ge\" \"date\" \"%1\"," " currentdate :value \"le\" \"date\" \"%2\")" " { keep; stop; }\n" ).arg( startDate.toString(Qt::ISODate), endDate.toString(Qt::ISODate) ); } script += QLatin1String("vacation "); script += addressesArgument; if ( notificationInterval > 0 ) script += QString::fromLatin1(":days %1 ").arg( notificationInterval ); if (!subject.trimmed().isEmpty()) { script += QString::fromLatin1(":subject \"%1\" ").arg(stringReplace(subject).trimmed()); } script += QString::fromLatin1("text:\n"); - script += dotstuff( messageText.isEmpty() ? VacationUtils::defaultMessageText() : messageText ); + script += dotstuff( messageText.isEmpty() ? KSieveUi::VacationUtils::defaultMessageText() : messageText ); script += QString::fromLatin1( "\n.\n;\n" ); return script; } +QString KSieveUi::VacationUtils::composeScript( const QString & messageText, bool active, + const QString &subject, + int notificationInterval, + const AddrSpecList & addrSpecs, + bool sendForSpam, const QString & domain, + const QDate & startDate, const QDate & endDate ) +{ + QStringList condition; + + if (startDate.isValid()) { + condition.append(QString::fromLatin1("currentdate :value \"ge\" \"date\" \"%1\"") + .arg(startDate.toString(Qt::ISODate))); + } + + if (endDate.isValid()) { + condition.append(QString::fromLatin1("currentdate :value \"le\" \"date\" \"%1\"") + .arg(endDate.toString(Qt::ISODate))); + } + + if (!sendForSpam) { + condition.append(QString::fromLatin1("not header :contains \"X-Spam-Flag\" \"YES\"")); + } + + if (!domain.isEmpty()) { + condition.append(QString::fromLatin1("address :domain :contains \"from\" \"%1\"").arg( domain )); + } + + QString addressesArgument; + QStringList aliases; + if ( !addrSpecs.empty() ) { + addressesArgument += QLatin1String(":addresses [ "); + QStringList sl; + AddrSpecList::const_iterator end = addrSpecs.constEnd(); + for ( AddrSpecList::const_iterator it = addrSpecs.begin() ; it != end; ++it ) { + sl.push_back( QLatin1Char('"') + (*it).asString().replace( QLatin1Char('\\'), QLatin1String("\\\\") ).replace( QLatin1Char('"'), QLatin1String("\\\"") ) + QLatin1Char('"') ); + aliases.push_back( (*it).asString() ); + } + addressesArgument += sl.join( QLatin1String(", ") ) + QLatin1String(" ] "); + } + + QString vacation(QLatin1String("vacation ")); + vacation += addressesArgument; + if ( notificationInterval > 0 ) + vacation += QString::fromLatin1(":days %1 ").arg(notificationInterval); + + if (!subject.trimmed().isEmpty()) { + vacation += QString::fromLatin1(":subject \"%1\" ").arg(stringReplace(subject).trimmed()); + } + + vacation += QString::fromLatin1("text:\n"); + vacation += dotstuff( messageText.isEmpty() ? VacationUtils::defaultMessageText() : messageText ); + vacation += QString::fromLatin1( "\n.\n;" ); + + QString script; + + if ( startDate.isValid() || endDate.isValid() ) { + script = QString::fromLatin1("require [\"vacation\", \"relational\", \"date\"];\n\n" ); + } else { + script = QString::fromLatin1("require \"vacation\";\n\n" ); + } + + if (condition.count() == 0) { + if (active) { + script += vacation; + } else { + script += QString::fromLatin1("if false\n{\n\t"); + script += vacation; + script += QLatin1String("\n}"); + } + } else { + if (active) { + script += QString::fromLatin1("if allof(%1)\n{\n\t").arg(condition.join(QLatin1String(", "))); + } else { + script += QString::fromLatin1("if false # allof(%1)\n{\n\t").arg(condition.join(QLatin1String(", "))); + } + script += vacation; + script += QLatin1String("\n}"); + } + + script += QLatin1String("\n"); + + return script; +} + + +QString KSieveUi::VacationUtils::mergeRequireLine(const QString &script, const QString scriptUpdate) +{ + const QByteArray scriptUTF8 = script.trimmed().toUtf8(); + const QByteArray scriptUpdateUTF8 = scriptUpdate.trimmed().toUtf8(); + + if (scriptUTF8.isEmpty()) { + return scriptUpdate; + } + + if (scriptUpdateUTF8.isEmpty()) { + return script; + } + + KSieve::Parser parser( scriptUTF8.begin(), + scriptUTF8.begin() + scriptUTF8.length() ); + KSieve::Parser parserUpdate( scriptUpdateUTF8.begin(), + scriptUpdateUTF8.begin() + scriptUpdateUTF8.length() ); + RequireExtractor rx, rxUpdate; + parser.setScriptBuilder(&rx); + parserUpdate.setScriptBuilder(&rxUpdate); + + int insert(0); + QStringList lines = script.split(QLatin1Char('\n')); + QSet requirements; + + if (parser.parse() && rx.commandFound()) { + insert = rx.lineStart(); + const int endOld(rx.lineEnd()); + for (int i=insert; i<=endOld; i++) { + lines.removeAt(insert); + } + requirements = rx.requirements().toSet(); + } + + if (parserUpdate.parse() && rxUpdate.commandFound()) { + requirements += rxUpdate.requirements().toSet(); + } + + if (requirements.count() > 1) { + QStringList req = requirements.toList(); + req.sort(); + lines.insert(insert, QString::fromLatin1("require [\"%1\"];").arg(req.join(QLatin1String("\", \"")))); + } else if (requirements.count() == 1) { + lines.insert(insert, QString::fromLatin1("require \"%1\";").arg(requirements.toList().first())); + } + + return lines.join(QLatin1String("\n")); +} + +QString KSieveUi::VacationUtils::updateVacationBlock(const QString &oldScript, const QString &newScript) +{ + const QByteArray oldScriptUTF8 = oldScript.trimmed().toUtf8(); + const QByteArray newScriptUTF8 = newScript.trimmed().toUtf8(); + + if (oldScriptUTF8.isEmpty()) { + return newScript; + } + + if (newScriptUTF8.isEmpty()) { + return oldScript; + } + + KSieve::Parser parserOld( oldScriptUTF8.begin(), + oldScriptUTF8.begin() + oldScriptUTF8.length() ); + KSieve::Parser parserNew( newScriptUTF8.begin(), + newScriptUTF8.begin() + newScriptUTF8.length() ); + VacationDataExtractor vdxOld, vdxNew; + RequireExtractor rx; + KSieveExt::MultiScriptBuilder tsb( &vdxOld , &rx ); + parserOld.setScriptBuilder(&tsb); + parserNew.setScriptBuilder(&vdxNew); + + int startOld(0); + + int startNew(vdxNew.lineStart()); + int endNew(vdxNew.lineEnd()); + + QStringList lines = oldScript.split(QLatin1Char('\n')); + + QString script; + if (parserOld.parse() && vdxOld.commandFound()) { + startOld = vdxOld.lineStart(); + const int endOld(vdxOld.lineEnd()); + for (int i=startOld; i<=endOld; i++) { + lines.removeAt(startOld); + } + } else { + if (rx.commandFound()) { // after require + startOld = rx.lineEnd() + 1; + } else { + startOld = 0; + } + } + + if (parserNew.parse() && vdxNew.commandFound()) { + const int startNew(vdxNew.lineStart()); + const int endNew(vdxNew.lineEnd()); + QStringList linesNew = newScript.split(QLatin1Char('\n')); + for(int i=endNew;i>=startNew;i--) { + lines.insert(startOld, linesNew.at(i)); + } + } + + return lines.join(QLatin1String("\n")); +} diff --git a/libksieve/ksieveui/vacation/vacationutils.h b/libksieve/ksieveui/vacation/vacationutils.h index 21965a8b66..d6b8c6f662 100644 --- a/libksieve/ksieveui/vacation/vacationutils.h +++ b/libksieve/ksieveui/vacation/vacationutils.h @@ -1,59 +1,63 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program 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. 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 */ #ifndef VACATIONUTILS_H #define VACATIONUTILS_H #include #include namespace KMime { namespace Types { struct AddrSpec; typedef QList AddrSpecList; } } class QDate; namespace KSieveUi { namespace VacationUtils { QString defaultMessageText(); QString defaultSubject(); int defaultNotificationInterval(); QStringList defaultMailAliases(); bool defaultSendForSpam(); QString defaultDomainName(); QDate defaultStartDate(); QDate defaultEndDate(); -QString composeScript( const QString & messageText, const QString &subject, +QString composeScript( const QString & messageText, bool active, const QString &subject, int notificationInterval, const KMime::Types::AddrSpecList & aliases, bool sendForSpam, const QString & excludeDomain, const QDate & startDate, const QDate & endDate ); -bool parseScript( const QString & script, QString & messageText, +bool parseScript( const QString & script, bool &active, QString & messageText, QString &subject, int & notificationInterval, QStringList & aliases, bool & sendForSpam, QString & domainName, QDate & startDate, QDate & endDate ); bool foundVacationScript(const QString & script); +QString mergeRequireLine(const QString &script1, const QString script2); + +QString updateVacationBlock(const QString &oldScript, const QString &newScript); + } } #endif // VACATIONUTILS_H diff --git a/libksieve/parser/parser.cpp b/libksieve/parser/parser.cpp index 35624526c5..df9ad7c7c2 100644 --- a/libksieve/parser/parser.cpp +++ b/libksieve/parser/parser.cpp @@ -1,650 +1,654 @@ /* -*- c++ -*- parser/parser.cpp This file is part of KSieve, the KDE internet mail/usenet news message filtering library. Copyright (c) 2002-2003 Marc Mutz KSieve 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. KSieve 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 #include #include #include #include #include #include // ULONG_MAX #include // isdigit +#include + namespace KSieve { // // // Parser Bridge implementation // // Parser::Parser( const char * scursor, const char * const send, int options ) : i( 0 ) { i = new Impl( scursor, send, options ); } Parser::~Parser() { delete i; i = 0; } void Parser::setScriptBuilder( ScriptBuilder * builder ) { assert( i ); i->mBuilder = builder; } ScriptBuilder * Parser::scriptBuilder() const { assert( i ); return i->mBuilder; } const Error & Parser::error() const { assert( i ); return i->error(); } bool Parser::parse() { assert( i ); return i->parse(); } } static inline unsigned long factorForQuantifier( char ch ) { switch ( ch ) { case 'g': case 'G': return 1024*1024*1024; case 'm': case 'M': return 1024*1024; case 'k': case 'K': return 1024; default: assert( 0 ); // lexer should prohibit this return 1; // make compiler happy } } static inline bool willOverflowULong( unsigned long result, unsigned long add ) { static const unsigned long maxULongByTen = (unsigned long)(ULONG_MAX / 10.0) ; return result > maxULongByTen || ULONG_MAX - 10 * result < add ; } namespace KSieve { // // // Parser Implementation // // Parser::Impl::Impl( const char * scursor, const char * const send, int options ) : mToken( Lexer::None ), lexer( scursor, send, options ), mBuilder( 0 ) { } bool Parser::Impl::isStringToken() const { return token() == Lexer::QuotedString || token() == Lexer::MultiLineString ; } bool Parser::Impl::isArgumentToken() const { return isStringToken() || token() == Lexer::Number || token() == Lexer::Tag || ( token() == Lexer::Special && mTokenValue == QLatin1String("[")) ; } bool Parser::Impl::obtainToken() { while ( !mToken && !lexer.atEnd() && !lexer.error() ) { mToken = lexer.nextToken( mTokenValue ); if ( lexer.error() ) break; // comments and line feeds are semantically invisible and may // appear anywhere, so we handle them here centrally: switch ( token() ) { case Lexer::HashComment: if ( scriptBuilder() ) scriptBuilder()->hashComment( tokenValue() ); consumeToken(); break; case Lexer::BracketComment: if ( scriptBuilder() ) scriptBuilder()->bracketComment( tokenValue() ); consumeToken(); break; case Lexer::LineFeeds: for ( unsigned int i = 0, end = tokenValue().toUInt() ; i < end ; ++i ) if ( scriptBuilder() ) // better check every iteration, b/c // we call out to ScriptBuilder, // where nasty things might happen! scriptBuilder()->lineFeed(); consumeToken(); break; default: ; // make compiler happy } } if ( lexer.error() && scriptBuilder() ) scriptBuilder()->error( lexer.error() ); return !lexer.error(); } bool Parser::Impl::parse() { // this is the entry point: START := command-list if ( !parseCommandList() ) return false; if ( !atEnd() ) { makeUnexpectedTokenError( Error::ExpectedCommand ); return false; } if ( scriptBuilder() ) scriptBuilder()->finished(); return true; } bool Parser::Impl::parseCommandList() { // our ABNF: // command-list := *comand while ( !atEnd() ) { if ( !obtainToken() ) return false; if ( token() == Lexer::None ) continue; if ( token() != Lexer::Identifier ) return true; if ( !parseCommand() ) { assert( error() ); return false; } } return true; } bool Parser::Impl::parseCommand() { // command := identifier arguments ( ";" / block ) // arguments := *argument [ test / test-list ] // block := "{" *command "}" // our ABNF: // block := "{" [ command-list ] "}" if ( atEnd() ) return false; // // identifier // if ( !obtainToken() || token() != Lexer::Identifier ) return false; - if ( scriptBuilder() ) - scriptBuilder()->commandStart( tokenValue() ); + if ( scriptBuilder() ) { + scriptBuilder()->commandStart( tokenValue(), lexer.line() ); + } consumeToken(); // // *argument // if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::MissingSemicolonOrBlock ); return false; } if ( isArgumentToken() && !parseArgumentList() ) { assert( error() ); return false; } // // test / test-list // if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::MissingSemicolonOrBlock ); return false; } if ( token() == Lexer::Special && tokenValue() == QLatin1String ("(")) { // test-list if ( !parseTestList() ) { assert( error() ); return false; } } else if ( token() == Lexer::Identifier ) { // should be test: if ( !parseTest() ) { assert( error() ); return false; } } // // ";" / block // if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::MissingSemicolonOrBlock ); return false; } if ( token() != Lexer::Special ) { makeUnexpectedTokenError( Error::ExpectedBlockOrSemicolon ); return false; } if ( tokenValue() == QLatin1String (";")) consumeToken(); else if ( tokenValue() == QLatin1String ("{")) { // block if ( !parseBlock() ) return false; // it's an error since we saw '{' } else { makeError( Error::MissingSemicolonOrBlock ); return false; } - if ( scriptBuilder() ) - scriptBuilder()->commandEnd(); + if ( scriptBuilder() ) { + scriptBuilder()->commandEnd(lexer.line()); + } return true; } bool Parser::Impl::parseArgumentList() { // our ABNF: // argument-list := *argument while ( !atEnd() ) { if ( !obtainToken() ) return false; if ( !isArgumentToken() ) return true; if ( !parseArgument() ) return !error(); } return true; } bool Parser::Impl::parseArgument() { // argument := string-list / number / tag if ( !obtainToken() || atEnd() ) return false; if ( token() == Lexer::Number ) { if ( !parseNumber() ) { assert( error() ); return false; } return true; } else if ( token() == Lexer::Tag ) { if ( scriptBuilder() ) scriptBuilder()->taggedArgument( tokenValue() ); consumeToken(); return true; } else if ( isStringToken() ) { if ( scriptBuilder() ) scriptBuilder()->stringArgument( tokenValue(), token() == Lexer::MultiLineString, QString() ); consumeToken(); return true; } else if ( token() == Lexer::Special && tokenValue() == QLatin1String("[")) { if ( !parseStringList() ) { assert( error() ); return false; } return true; } return false; } bool Parser::Impl::parseTestList() { // test-list := "(" test *("," test) ")" if ( !obtainToken() || atEnd() ) return false; if ( token() != Lexer::Special || tokenValue() != QLatin1String("(")) return false; if ( scriptBuilder() ) scriptBuilder()->testListStart(); consumeToken(); // generic while/switch construct for comma-separated lists. See // parseStringList() for another one. Any fix here is like to apply there, too. bool lastWasComma = true; while ( !atEnd() ) { if ( !obtainToken() ) return false; switch ( token() ) { case Lexer::None: break; case Lexer::Special: assert( tokenValue().length() == 1 ); assert( tokenValue()[0].toLatin1() ); switch ( tokenValue()[0].toLatin1() ) { case ')': consumeToken(); if ( lastWasComma ) { makeError( Error::ConsecutiveCommasInTestList ); return false; } if ( scriptBuilder() ) scriptBuilder()->testListEnd(); return true; case ',': consumeToken(); if ( lastWasComma ) { makeError( Error::ConsecutiveCommasInTestList ); return false; } lastWasComma = true; break; default: makeError( Error::NonStringInStringList ); return false; } break; case Lexer::Identifier: if ( !lastWasComma ) { makeError( Error::MissingCommaInTestList ); return false; } else { lastWasComma = false; if ( !parseTest() ) { assert( error() ); return false; } } break; default: makeUnexpectedTokenError( Error::NonTestInTestList ); return false; } } makeError( Error::PrematureEndOfTestList ); return false; } bool Parser::Impl::parseTest() { // test := identifier arguments // arguments := *argument [ test / test-list ] // // identifier // if ( !obtainToken() || atEnd() ) return false; if ( token() != Lexer::Identifier ) return false; if ( scriptBuilder() ) scriptBuilder()->testStart( tokenValue() ); consumeToken(); // // *argument // if ( !obtainToken() ) return false; if ( atEnd() ) // a test w/o args goto TestEnd; if ( isArgumentToken() && !parseArgumentList() ) { assert( error() ); return false; } // // test / test-list // if ( !obtainToken() ) return false; if ( atEnd() ) // a test w/o nested tests goto TestEnd; if ( token() == Lexer::Special && tokenValue() == QLatin1String("(")) { // test-list if ( !parseTestList() ) { assert( error() ); return false; } } else if ( token() == Lexer::Identifier ) { // should be test: if ( !parseTest() ) { assert( error() ); return false; } } TestEnd: if ( scriptBuilder() ) scriptBuilder()->testEnd(); return true; } bool Parser::Impl::parseBlock() { // our ABNF: // block := "{" [ command-list ] "}" if ( !obtainToken() || atEnd() ) return false; if ( token() != Lexer::Special || tokenValue() != QLatin1String("{")) return false; if ( scriptBuilder() ) - scriptBuilder()->blockStart(); + scriptBuilder()->blockStart(lexer.line()); consumeToken(); if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::PrematureEndOfBlock ); return false; } if ( token() == Lexer::Identifier ) { if ( !parseCommandList() ) { assert( error() ); return false; } } if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::PrematureEndOfBlock ); return false; } if ( token() != Lexer::Special || tokenValue() != QLatin1String("}")) { makeError( Error::NonCommandInCommandList ); return false; } if ( scriptBuilder() ) - scriptBuilder()->blockEnd(); + scriptBuilder()->blockEnd(lexer.line()); consumeToken(); return true; } bool Parser::Impl::parseStringList() { // string-list := "[" string *("," string) "]" / string // ;; if there is only a single string, the brackets are optional // // However, since strings are already handled separately from // string lists in parseArgument(), our ABNF is modified to: // string-list := "[" string *("," string) "]" if ( !obtainToken() || atEnd() ) return false; if ( token() != Lexer::Special || tokenValue() != QLatin1String("[") ) return false; if ( scriptBuilder() ) scriptBuilder()->stringListArgumentStart(); consumeToken(); // generic while/switch construct for comma-separated lists. See // parseTestList() for another one. Any fix here is like to apply there, too. bool lastWasComma = true; while ( !atEnd() ) { if ( !obtainToken() ) return false; switch ( token() ) { case Lexer::None: break; case Lexer::Special: assert( tokenValue().length() == 1 ); switch ( tokenValue()[0].toLatin1() ) { case ']': consumeToken(); if ( lastWasComma ) { makeError( Error::ConsecutiveCommasInStringList ); return false; } if ( scriptBuilder() ) scriptBuilder()->stringListArgumentEnd(); return true; case ',': consumeToken(); if ( lastWasComma ) { makeError( Error::ConsecutiveCommasInStringList ); return false; } lastWasComma = true; break; default: makeError( Error::NonStringInStringList ); return false; } break; case Lexer::QuotedString: case Lexer::MultiLineString: if ( !lastWasComma ) { makeError( Error::MissingCommaInStringList ); return false; } lastWasComma = false; if ( scriptBuilder() ) scriptBuilder()->stringListEntry( tokenValue(), token() == Lexer::MultiLineString, QString() ); consumeToken(); break; default: makeError( Error::NonStringInStringList ); return false; } } makeError( Error::PrematureEndOfStringList ); return false; } bool Parser::Impl::parseNumber() { // The lexer returns the number including the quantifier as a // single token value. Here, we split is an check that the number // is not out of range: if ( !obtainToken() || atEnd() ) return false; if ( token() != Lexer::Number ) return false; // number: unsigned long result = 0; int i = 0; const QByteArray s = tokenValue().toLatin1(); for ( const int len = s.length() ; i < len && isdigit( s[i] ) ; ++i ) { const unsigned long digitValue = s[i] - QLatin1Char('0').toLatin1() ; if ( willOverflowULong( result, digitValue ) ) { makeError( Error::NumberOutOfRange ); return false; } else { result *= 10 ; result += digitValue ; } } // optional quantifier: char quantifier = '\0'; if ( i < s.length() ) { assert( i + 1 == s.length() ); quantifier = s[i]; const unsigned long factor = factorForQuantifier( quantifier ); if ( result > double(ULONG_MAX) / double(factor) ) { makeError( Error::NumberOutOfRange ); return false; } result *= factor; } if ( scriptBuilder() ) scriptBuilder()->numberArgument( result, quantifier ); consumeToken(); return true; } } // namespace KSieve diff --git a/libksieve/tests/parsertest.cpp b/libksieve/tests/parsertest.cpp index 57660c85b3..82eb8e1d9a 100644 --- a/libksieve/tests/parsertest.cpp +++ b/libksieve/tests/parsertest.cpp @@ -1,668 +1,668 @@ /* -*- c++ -*- tests/parsertest.cpp This file is part of the testsuite of KSieve, the KDE internet mail/usenet news message filtering library. Copyright (c) 2003 Marc Mutz KSieve 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. KSieve 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 // SIZEOF_UNSIGNED_LONG #include using KSieve::Parser; #include #include #include #include #include using std::cout; using std::cerr; using std::endl; #include enum BuilderMethod { TaggedArgument, StringArgument, NumberArgument, CommandStart, CommandEnd, TestStart, TestEnd, TestListStart, TestListEnd, BlockStart, BlockEnd, StringListArgumentStart, StringListEntry, StringListArgumentEnd, HashComment, BracketComment, Error, Finished }; static const unsigned int MAX_RESPONSES = 100; struct TestCase { const char * name; const char * script; struct Response { BuilderMethod method; const char * string; bool boolean; } responses[MAX_RESPONSES]; } testCases[] = { // // single commands: // { "Null script", 0, { { Finished, 0, false } } }, { "Empty script", "", { { Finished, 0, false } } }, { "WS-only script", " \t\n\r\n", { { Finished, 0, false } } }, { "Bare hash comment", "#comment", { { HashComment, "comment", false }, { Finished, 0, false } } }, { "Bare bracket comment", "/*comment*/", { { BracketComment, "comment", false }, { Finished, 0, false } } }, { "Bare command", "command;", { { CommandStart, "command", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "Bare command - missing semicolon", "command", { { CommandStart, "command", false }, { Error, "MissingSemicolonOrBlock", false } } }, { "surrounded by bracket comments", "/*comment*/command/*comment*/;/*comment*/", { { BracketComment, "comment", false }, { CommandStart, "command", false }, { BracketComment, "comment", false }, { CommandEnd, 0, false }, { BracketComment, "comment", false }, { Finished, 0, false } } }, { "surrounded by hash comments", "#comment\ncommand#comment\n;#comment", { { HashComment, "comment", false }, { CommandStart, "command", false }, { HashComment, "comment", false }, { CommandEnd, 0, false }, { HashComment, "comment", false }, { Finished, 0, false } } }, { "single tagged argument", "command :tag;", { { CommandStart, "command", false }, { TaggedArgument, "tag", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single tagged argument - missing semicolon", "command :tag", { { CommandStart, "command", false }, { TaggedArgument, "tag", false }, { Error, "MissingSemicolonOrBlock", false } } }, { "single string argument - quoted string", "command \"string\";", { { CommandStart, "command", false }, { StringArgument, "string", false /*quoted*/ }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single string argument - multi-line string", "command text:\nstring\n.\n;", { { CommandStart, "command", false }, { StringArgument, "string", true /*multiline*/ }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single number argument - 100", "command 100;", { { CommandStart, "command", false }, { NumberArgument, "100 ", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single number argument - 100k", "command 100k;", { { CommandStart, "command", false }, { NumberArgument, "102400k", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single number argument - 100M", "command 100M;", { { CommandStart, "command", false }, { NumberArgument, "104857600M", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single number argument - 2G", "command 2G;", { { CommandStart, "command", false }, { NumberArgument, "2147483648G", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, #if SIZEOF_UNSIGNED_LONG == 8 # define ULONG_MAX_STRING "18446744073709551615" # define ULONG_MAXP1_STRING "18446744073709551616" #elif SIZEOF_UNSIGNED_LONG == 4 # define ULONG_MAX_STRING "4294967295" # define ULONG_MAXP1_STRING "4G" #else # error sizeof( unsigned long ) != 4 && sizeof( unsigned long ) != 8 ??? #endif { "single number argument - ULONG_MAX + 1", "command " ULONG_MAXP1_STRING ";", { { CommandStart, "command", false }, { Error, "NumberOutOfRange", false } } }, { "single number argument - ULONG_MAX", "command " ULONG_MAX_STRING ";", { { CommandStart, "command", false }, { NumberArgument, ULONG_MAX_STRING " ", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single one-element string list argument - quoted string", "command [\"string\"];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", false /*quoted*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single one-element string list argument - multi-line string", "command [text:\nstring\n.\n];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", true /*multiline*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single two-element string list argument - quoted strings", "command [\"string\",\"string\"];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", false /*quoted*/ }, { StringListEntry, "string", false /*quoted*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single two-element string list argument - multi-line strings", "command [text:\nstring\n.\n,text:\nstring\n.\n];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", true /*multiline*/ }, { StringListEntry, "string", true /*multiline*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single two-element string list argument - quoted + multi-line strings", "command [\"string\",text:\nstring\n.\n];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", false /*quoted*/ }, { StringListEntry, "string", true /*multiline*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single two-element string list argument - multi-line + quoted strings", "command [text:\nstring\n.\n,\"string\"];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", true /*multiline*/ }, { StringListEntry, "string", false /*quoted*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single bare test argument", "command test;", { { CommandStart, "command", false }, { TestStart, "test", false }, { TestEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "one-element test list argument", "command(test);", { { CommandStart, "command", false }, { TestListStart, 0, false }, { TestStart, "test", false }, { TestEnd, 0, false }, { TestListEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "two-element test list argument", "command(test,test);", { { CommandStart, "command", false }, { TestListStart, 0, false }, { TestStart, "test", false }, { TestEnd, 0, false }, { TestStart, "test", false }, { TestEnd, 0, false }, { TestListEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "zero-element block", "command{}", { { CommandStart, "command", false }, { BlockStart, 0, false }, { BlockEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "one-element block", "command{command;}", { { CommandStart, "command", false }, { BlockStart, 0, false }, { CommandStart, "command", false }, { CommandEnd, 0, false }, { BlockEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "two-element block", "command{command;command;}", { { CommandStart, "command", false }, { BlockStart, 0, false }, { CommandStart, "command", false }, { CommandEnd, 0, false }, { CommandStart, "command", false }, { CommandEnd, 0, false }, { BlockEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "command with a test with a test with a test", "command test test test;", { { CommandStart, "command", false }, { TestStart, "test", false }, { TestStart, "test", false }, { TestStart, "test", false }, { TestEnd, 0, false }, { TestEnd, 0, false }, { TestEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, }; static const int numTestCases = sizeof testCases / sizeof *testCases ; // Prints out the parse tree in XML-like format. For visual inspection // (manual tests). class PrintingScriptBuilder : public KSieve::ScriptBuilder { public: PrintingScriptBuilder() : KSieve::ScriptBuilder(), indent( 0 ) { write( "" ); } private: int indent; void write( const char * msg ) { for ( int i = 2*indent ; i > 0 ; --i ) cout << " "; cout << msg << endl; } void write( const QByteArray & key, const QString & value ) { if ( value.isEmpty() ) { write( QByteArray(QByteArray("<") + key + QByteArray("/>")) ); return; } write( QByteArray(QByteArray("<") + key + QByteArray(">")) ); ++indent; write( value.toUtf8().data() ); --indent; write( QByteArray(QByteArray("")) ); } }; // verifes that methods get called with expected arguments (and in // expected sequence) as specified by the TestCase. For automated // tests. class VerifyingScriptBuilder : public KSieve::ScriptBuilder { public: VerifyingScriptBuilder( const TestCase & testCase ) : KSieve::ScriptBuilder(), mNextResponse( 0 ), mTestCase( testCase ), mOk( true ) { } virtual ~VerifyingScriptBuilder() {} bool ok() const { return mOk; } void taggedArgument( const QString & tag ) { checkIs( TaggedArgument ); checkEquals( tag ); ++mNextResponse; } void stringArgument( const QString & string, bool multiline, const QString & /*fixme*/ ) { checkIs( StringArgument ); checkEquals( string ); checkEquals( multiline ); ++mNextResponse; } void numberArgument( unsigned long number, char quantifier ) { checkIs( NumberArgument ); checkEquals( QString::number( number ) + ( quantifier ? quantifier : ' ' ) ); ++mNextResponse; } - void commandStart( const QString & identifier ) { + void commandStart( const QString & identifier, int lineNumber ) { checkIs( CommandStart ); checkEquals( identifier ); ++mNextResponse; } - void commandEnd() { + void commandEnd(int lineNumber) { checkIs( CommandEnd ); ++mNextResponse; } void testStart( const QString & identifier ) { checkIs( TestStart ); checkEquals( identifier ); ++mNextResponse; } void testEnd() { checkIs( TestEnd ); ++mNextResponse; } void testListStart() { checkIs( TestListStart ); ++mNextResponse; } void testListEnd() { checkIs( TestListEnd ); ++mNextResponse; } - void blockStart() { + void blockStart(int lineNumber) { checkIs( BlockStart ); ++mNextResponse; } - void blockEnd() { + void blockEnd(int lineNumber) { checkIs( BlockEnd ); ++mNextResponse; } void stringListArgumentStart() { checkIs( StringListArgumentStart ); ++mNextResponse; } void stringListEntry( const QString & string, bool multiLine, const QString & /*fixme*/ ) { checkIs( StringListEntry ); checkEquals( string ); checkEquals( multiLine ); ++mNextResponse; } void stringListArgumentEnd() { checkIs( StringListArgumentEnd ); ++mNextResponse; } void hashComment( const QString & comment ) { checkIs( HashComment ); checkEquals( comment ); ++mNextResponse; } void bracketComment( const QString & comment ) { checkIs( BracketComment ); checkEquals( comment ); ++mNextResponse; } void lineFeed() { // FIXME } void error( const KSieve::Error & error ) { checkIs( Error ); checkEquals( QString( KSieve::Error::typeToString( error.type() ) ) ); ++mNextResponse; } void finished() { checkIs( Finished ); //++mNextResponse (no!) } private: const TestCase::Response & currentResponse() const { assert( mNextResponse <= MAX_RESPONSES ); return mTestCase.responses[mNextResponse]; } void checkIs( BuilderMethod m ) { if ( currentResponse().method != m ) { cerr << " expected method " << (int)currentResponse().method << ", got " << (int)m; mOk = false; } } void checkEquals( const QString & s ) { if ( s != QString::fromUtf8( currentResponse().string ) ) { cerr << " expected string arg \"" << ( currentResponse().string ? currentResponse().string : "" ) << "\", got \"" << ( s.isNull() ? "" : s.toUtf8().data() ) << "\""; mOk = false; } } void checkEquals( bool b ) { if ( b != currentResponse().boolean ) { cerr << " expected boolean arg <" << currentResponse().boolean << ">, got <" << b << ">"; mOk = false; } } unsigned int mNextResponse; const TestCase & mTestCase; bool mOk; }; int main( int argc, char * argv[] ) { if ( argc == 2 ) { // manual test const char * scursor = argv[1]; const char * const send = argv[1] + qstrlen( argv[1] ); Parser parser( scursor, send ); PrintingScriptBuilder psb; parser.setScriptBuilder( &psb ); if ( parser.parse() ) cout << "ok" << endl; else cout << "bad" << endl; } else if ( argc == 1 ) { // automated test bool success = true; for ( int i = 0 ; i < numTestCases ; ++i ) { const TestCase & t = testCases[i]; cerr << t.name << ":"; VerifyingScriptBuilder v( t ); Parser p( t.script, t.script + qstrlen( t.script ) ); p.setScriptBuilder( &v ); const bool ok = p.parse(); if ( v.ok() ) if ( ok ) cerr << " ok"; else cerr << " xfail"; else success = false; cerr << endl; } if ( !success ) exit( 1 ); } else { // usage error cerr << "usage: parsertest [ ]" << endl; exit( 1 ); } return 0; }