diff --git a/libksieve/autotests/parsertest.cpp b/libksieve/autotests/parsertest.cpp index 663d896f08..b156533b6c 100644 --- a/libksieve/autotests/parsertest.cpp +++ b/libksieve/autotests/parsertest.cpp @@ -1,743 +1,751 @@ /* -*- 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 <../src/ksieve/parser.h> using KSieve::Parser; #include <../src/ksieve/error.h> #include <../src/ksieve/scriptbuilder.h> #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", Q_NULLPTR, { { Finished, Q_NULLPTR, false } } }, { "Empty script", "", { { Finished, Q_NULLPTR, false } } }, { "WS-only script", " \t\n\r\n", { { Finished, Q_NULLPTR, false } } }, { "Bare hash comment", "#comment", { { HashComment, "comment", false }, { Finished, Q_NULLPTR, false } } }, { "Bare bracket comment", "/*comment*/", { { BracketComment, "comment", false }, { Finished, Q_NULLPTR, false } } }, { "Bare command", "command;", { { CommandStart, "command", false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, 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, Q_NULLPTR, false }, { BracketComment, "comment", false }, { Finished, Q_NULLPTR, false } } }, { "surrounded by hash comments", "#comment\ncommand#comment\n;#comment", { { HashComment, "comment", false }, { CommandStart, "command", false }, { HashComment, "comment", false }, { CommandEnd, Q_NULLPTR, false }, { HashComment, "comment", false }, { Finished, Q_NULLPTR, false } } }, { "single tagged argument", "command :tag;", { { CommandStart, "command", false }, { TaggedArgument, "tag", false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, 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, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single string argument - multi-line string", "command text:\nstring\n.\n;", { { CommandStart, "command", false }, { StringArgument, "string", true /*multiline*/ }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single number argument - 100", "command 100;", { { CommandStart, "command", false }, { NumberArgument, "100 ", false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single number argument - 100k", "command 100k;", { { CommandStart, "command", false }, { NumberArgument, "102400k", false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single number argument - 100M", "command 100M;", { { CommandStart, "command", false }, { NumberArgument, "104857600M", false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single number argument - 2G", "command 2G;", { { CommandStart, "command", false }, { NumberArgument, "2147483648G", false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, 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, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single one-element string list argument - quoted string", "command [\"string\"];", { { CommandStart, "command", false }, { StringListArgumentStart, Q_NULLPTR, false }, { StringListEntry, "string", false /*quoted*/ }, { StringListArgumentEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single one-element string list argument - multi-line string", "command [text:\nstring\n.\n];", { { CommandStart, "command", false }, { StringListArgumentStart, Q_NULLPTR, false }, { StringListEntry, "string", true /*multiline*/ }, { StringListArgumentEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single two-element string list argument - quoted strings", "command [\"string\",\"string\"];", { { CommandStart, "command", false }, { StringListArgumentStart, Q_NULLPTR, false }, { StringListEntry, "string", false /*quoted*/ }, { StringListEntry, "string", false /*quoted*/ }, { StringListArgumentEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single two-element string list argument - multi-line strings", "command [text:\nstring\n.\n,text:\nstring\n.\n];", { { CommandStart, "command", false }, { StringListArgumentStart, Q_NULLPTR, false }, { StringListEntry, "string", true /*multiline*/ }, { StringListEntry, "string", true /*multiline*/ }, { StringListArgumentEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single two-element string list argument - quoted + multi-line strings", "command [\"string\",text:\nstring\n.\n];", { { CommandStart, "command", false }, { StringListArgumentStart, Q_NULLPTR, false }, { StringListEntry, "string", false /*quoted*/ }, { StringListEntry, "string", true /*multiline*/ }, { StringListArgumentEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single two-element string list argument - multi-line + quoted strings", "command [text:\nstring\n.\n,\"string\"];", { { CommandStart, "command", false }, { StringListArgumentStart, Q_NULLPTR, false }, { StringListEntry, "string", true /*multiline*/ }, { StringListEntry, "string", false /*quoted*/ }, { StringListArgumentEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "single bare test argument", "command test;", { { CommandStart, "command", false }, { TestStart, "test", false }, { TestEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "one-element test list argument", "command(test);", { { CommandStart, "command", false }, { TestListStart, Q_NULLPTR, false }, { TestStart, "test", false }, { TestEnd, Q_NULLPTR, false }, { TestListEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "two-element test list argument", "command(test,test);", { { CommandStart, "command", false }, { TestListStart, Q_NULLPTR, false }, { TestStart, "test", false }, { TestEnd, Q_NULLPTR, false }, { TestStart, "test", false }, { TestEnd, Q_NULLPTR, false }, { TestListEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "zero-element block", "command{}", { { CommandStart, "command", false }, { BlockStart, Q_NULLPTR, false }, { BlockEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "one-element block", "command{command;}", { { CommandStart, "command", false }, { BlockStart, Q_NULLPTR, false }, { CommandStart, "command", false }, { CommandEnd, Q_NULLPTR, false }, { BlockEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, false } } }, { "two-element block", "command{command;command;}", { { CommandStart, "command", false }, { BlockStart, Q_NULLPTR, false }, { CommandStart, "command", false }, { CommandEnd, Q_NULLPTR, false }, { CommandStart, "command", false }, { CommandEnd, Q_NULLPTR, false }, { BlockEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, 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, Q_NULLPTR, false }, { TestEnd, Q_NULLPTR, false }, { TestEnd, Q_NULLPTR, false }, { CommandEnd, Q_NULLPTR, false }, { Finished, Q_NULLPTR, 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) Q_DECL_OVERRIDE { checkIs(TaggedArgument); checkEquals(tag); ++mNextResponse; } void stringArgument(const QString &string, bool multiline, const QString & /*fixme*/) Q_DECL_OVERRIDE { checkIs(StringArgument); checkEquals(string); checkEquals(multiline); ++mNextResponse; } void numberArgument(unsigned long number, char quantifier) Q_DECL_OVERRIDE { checkIs(NumberArgument); checkEquals(QString::number(number) + (quantifier ? quantifier : ' ')); ++mNextResponse; } - void commandStart(const QString &identifier) Q_DECL_OVERRIDE { + void commandStart(const QString &identifier, int lineNumber) Q_DECL_OVERRIDE { + Q_UNUSED(lineNumber); checkIs(CommandStart); checkEquals(identifier); ++mNextResponse; } - void commandEnd() Q_DECL_OVERRIDE { + void commandEnd(int lineNumber) Q_DECL_OVERRIDE { + Q_UNUSED(lineNumber); checkIs(CommandEnd); ++mNextResponse; } void testStart(const QString &identifier) Q_DECL_OVERRIDE { checkIs(TestStart); checkEquals(identifier); ++mNextResponse; } void testEnd() Q_DECL_OVERRIDE { checkIs(TestEnd); ++mNextResponse; } void testListStart() Q_DECL_OVERRIDE { checkIs(TestListStart); ++mNextResponse; } void testListEnd() Q_DECL_OVERRIDE { checkIs(TestListEnd); ++mNextResponse; } - void blockStart() Q_DECL_OVERRIDE { + void blockStart(int lineNumber) Q_DECL_OVERRIDE { + Q_UNUSED(lineNumber); checkIs(BlockStart); ++mNextResponse; } - void blockEnd() Q_DECL_OVERRIDE { + void blockEnd(int lineNumber) Q_DECL_OVERRIDE { + Q_UNUSED(lineNumber); checkIs(BlockEnd); ++mNextResponse; } void stringListArgumentStart() Q_DECL_OVERRIDE { checkIs(StringListArgumentStart); ++mNextResponse; } void stringListEntry(const QString &string, bool multiLine, const QString & /*fixme*/) Q_DECL_OVERRIDE { checkIs(StringListEntry); checkEquals(string); checkEquals(multiLine); ++mNextResponse; } void stringListArgumentEnd() Q_DECL_OVERRIDE { checkIs(StringListArgumentEnd); ++mNextResponse; } void hashComment(const QString &comment) Q_DECL_OVERRIDE { checkIs(HashComment); checkEquals(comment); ++mNextResponse; } void bracketComment(const QString &comment) Q_DECL_OVERRIDE { checkIs(BracketComment); checkEquals(comment); ++mNextResponse; } void lineFeed() Q_DECL_OVERRIDE { // FIXME } void error(const KSieve::Error &error) Q_DECL_OVERRIDE { checkIs(Error); checkEquals(QString(KSieve::Error::typeToString(error.type()))); ++mNextResponse; } void finished() Q_DECL_OVERRIDE { checkIs(Finished); //++mNextResponse (no!) } private: const TestCase::Response ¤tResponse() 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; } diff --git a/libksieve/src/ksieve/scriptbuilder.h b/libksieve/src/ksieve/scriptbuilder.h index eb7c211940..1adb1b448b 100644 --- a/libksieve/src/ksieve/scriptbuilder.h +++ b/libksieve/src/ksieve/scriptbuilder.h @@ -1,82 +1,82 @@ /* -*- 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/src/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp b/libksieve/src/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp index 7c21003436..303e915a5b 100644 --- a/libksieve/src/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp +++ b/libksieve/src/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp @@ -1,216 +1,220 @@ /* Copyright (c) 2012-2015 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 "libksieve_debug.h" using namespace KSieveUi; XMLPrintingScriptBuilder::XMLPrintingScriptBuilder() : KSieve::ScriptBuilder(), mIsAction(false) { write(QStringLiteral("")); write(QStringLiteral("")); } void XMLPrintingScriptBuilder::write(const QString &msg) { mResult += msg; } void XMLPrintingScriptBuilder::write(const QString &key, const QString &value) { if (value.isEmpty()) { write(QStringLiteral("<%1>").arg(key)); return; } write(QStringLiteral("<%1>").arg(key)); write(value); write(QStringLiteral("").arg(key)); } void XMLPrintingScriptBuilder::write(const QString &key, const QString &attribute, const QString &value) { if (value.isEmpty()) { write(QStringLiteral("<%1/>").arg(key)); return; } if (attribute.isEmpty()) { write(QStringLiteral("<%1>").arg(key)); } else { write(QStringLiteral("<%1 %2>").arg(key).arg(attribute)); } write(value); write(QStringLiteral("").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)) { qCDebug(LIBKSIEVE_LOG) << "Unable to load document.Parse error in line " << errorRow << ", col " << errorCol << ": " << errorMsg; qCDebug(LIBKSIEVE_LOG) << " mResult" << mResult; } return doc; } diff --git a/libksieve/src/ksieveui/scriptsparsing/xmlprintingscriptbuilder.h b/libksieve/src/ksieveui/scriptsparsing/xmlprintingscriptbuilder.h index 87d0dca042..d5f4130b3a 100644 --- a/libksieve/src/ksieveui/scriptsparsing/xmlprintingscriptbuilder.h +++ b/libksieve/src/ksieveui/scriptsparsing/xmlprintingscriptbuilder.h @@ -1,73 +1,73 @@ /* Copyright (c) 2012-2015 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 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) Q_DECL_OVERRIDE; void stringArgument(const QString &string, bool multiLine, const QString & /*fixme*/) Q_DECL_OVERRIDE; void numberArgument(unsigned long number, char quantifier) Q_DECL_OVERRIDE; - void commandStart(const QString &identifier) Q_DECL_OVERRIDE; - void commandEnd() Q_DECL_OVERRIDE; + void commandStart(const QString &identifier, int lineNumber) Q_DECL_OVERRIDE; + void commandEnd(int lineNumber) Q_DECL_OVERRIDE; void testStart(const QString &identifier) Q_DECL_OVERRIDE; void testEnd() Q_DECL_OVERRIDE; void testListStart() Q_DECL_OVERRIDE; void testListEnd() Q_DECL_OVERRIDE; - void blockStart() Q_DECL_OVERRIDE; - void blockEnd() Q_DECL_OVERRIDE; + void blockStart(int lineNumber) Q_DECL_OVERRIDE; + void blockEnd(int lineNumber) Q_DECL_OVERRIDE; void stringListArgumentStart() Q_DECL_OVERRIDE; void stringListArgumentEnd() Q_DECL_OVERRIDE; void stringListEntry(const QString &string, bool multiline, const QString &hashComment) Q_DECL_OVERRIDE; void hashComment(const QString &comment) Q_DECL_OVERRIDE; void bracketComment(const QString &comment) Q_DECL_OVERRIDE; void lineFeed() Q_DECL_OVERRIDE; void error(const KSieve::Error &error) Q_DECL_OVERRIDE; void finished() Q_DECL_OVERRIDE; 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/src/ksieveui/vacation/vacationscriptextractor.cpp b/libksieve/src/ksieveui/vacation/vacationscriptextractor.cpp index e6071ba232..e51926c81d 100644 --- a/libksieve/src/ksieveui/vacation/vacationscriptextractor.cpp +++ b/libksieve/src/ksieveui/vacation/vacationscriptextractor.cpp @@ -1,135 +1,263 @@ /* Copyright (c) 2013-2015 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" //TODO: add unittests for VacationDataExtractor using namespace KSieveUi; VacationDataExtractor::VacationDataExtractor() - : KSieve::ScriptBuilder(), - mContext(None), - mNotificationInterval(0) + : KSieve::ScriptBuilder() + , mContext(None) + , mNotificationInterval(0) + , mActive(true) + , mInIfBlock(false) + , mFoundInBlock(false) + , mBlockLevel(0) + , mLineStart(0) + , mLineEnd(0) + , mMailAction(VacationUtils::Keep) + , mMailActionContext(None) { qCDebug(LIBKSIEVE_LOG); } VacationDataExtractor::~VacationDataExtractor() { } -void VacationDataExtractor::commandStart(const QString &identifier) +void VacationDataExtractor::commandStart(const QString &identifier, int lineNumber) { - qCDebug(LIBKSIEVE_LOG) << "( \"" << identifier << "\" )"; - if (identifier != QLatin1String("vacation")) { + qCDebug(LIBKSIEVE_LOG) << "(\"" << identifier << "\")"; + if (identifier == QStringLiteral("if") && mContext == None) { + mContext = IfBlock; + mLineStart = lineNumber; + mInIfBlock = true; + } + + if (commandFound() && (!mFoundInBlock || mBlockLevel > 0)) { + if (identifier == QStringLiteral("discard")) { + mMailAction = VacationUtils::Discard; + } else if (identifier == QStringLiteral("redirect")) { + mMailAction = VacationUtils::Sendto; + mMailActionContext = RedirectCommand; + } + } + + if (identifier != QStringLiteral("vacation")) { return; } + + if (mContext != IfBlock) { + mLineStart = lineNumber; + } + reset(); mContext = VacationCommand; + mFoundInBlock = (mBlockLevel > 0); } -void VacationDataExtractor::commandEnd() +void VacationDataExtractor::commandEnd(int lineNumber) { qCDebug(LIBKSIEVE_LOG); - mContext = None; + if (mContext != None && mContext != IfBlock && mContext != VacationEnd) { + mContext = VacationEnd; + mLineEnd = lineNumber; + } + mMailActionContext = None; } void VacationDataExtractor::error(const KSieve::Error &e) { qCDebug(LIBKSIEVE_LOG) << e.asString() << "@" << e.line() << "," << e.column(); } void VacationDataExtractor::finished() { } +void VacationDataExtractor::testStart(const QString &test) +{ + if (mContext == IfBlock) { + if (test == QStringLiteral("true") || test == QStringLiteral("false")) { + mActive = (test == QStringLiteral("true")); + mIfComment = QString(); + } + } +} + +void VacationDataExtractor::hashComment(const QString &comment) +{ + if (mContext == IfBlock) { + mIfComment += comment; + } +} + +void VacationDataExtractor::blockStart(int lineNumber) +{ + Q_UNUSED(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) { - qCDebug(LIBKSIEVE_LOG) << "( \"" << tag << "\" )"; + qCDebug(LIBKSIEVE_LOG) << "(\"" << tag << "\")"; + if (mMailActionContext == RedirectCommand) { + if (tag == QStringLiteral("copy")) { + mMailAction = VacationUtils::CopyTo; + } + } if (mContext != VacationCommand) { return; } - if (tag == QLatin1String("days")) { + if (tag == QStringLiteral("days")) { mContext = Days; - } else if (tag == QLatin1String("addresses")) { + } else if (tag == QStringLiteral("addresses")) { mContext = Addresses; - } else if (tag == QLatin1String("subject")) { + } else if (tag == QStringLiteral("subject")) { mContext = Subject; } } void VacationDataExtractor::stringArgument(const QString &string, bool, const QString &) { - qCDebug(LIBKSIEVE_LOG) << "( \"" << string << "\" )"; + qCDebug(LIBKSIEVE_LOG) << "(\"" << 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; } + if (mMailActionContext == RedirectCommand) { + mMailActionRecipient = string; + } } void VacationDataExtractor::numberArgument(unsigned long number, char) { - qCDebug(LIBKSIEVE_LOG) << "( \"" << number << "\" )"; + qCDebug(LIBKSIEVE_LOG) << "(\"" << 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 &) { - qCDebug(LIBKSIEVE_LOG) << "( \"" << string << "\" )"; + qCDebug(LIBKSIEVE_LOG) << "(\"" << string << "\")"; if (mContext != Addresses) { return; } mAliases.push_back(string); } void VacationDataExtractor::stringListArgumentEnd() { qCDebug(LIBKSIEVE_LOG); if (mContext != Addresses) { return; } mContext = VacationCommand; } void VacationDataExtractor::reset() { qCDebug(LIBKSIEVE_LOG); mContext = None; + mMailAction = VacationUtils::Keep; + mMailActionRecipient = QString(); 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 == QStringLiteral("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) +{ + qCDebug(LIBKSIEVE_LOG) << 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; +} diff --git a/libksieve/src/ksieveui/vacation/vacationscriptextractor.h b/libksieve/src/ksieveui/vacation/vacationscriptextractor.h index e34b20b058..fac2154546 100644 --- a/libksieve/src/ksieveui/vacation/vacationscriptextractor.h +++ b/libksieve/src/ksieveui/vacation/vacationscriptextractor.h @@ -1,560 +1,729 @@ /* Copyright (c) 2013-2015 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 "vacationutils.h" #include #include #include #include #include "libksieve_debug.h" #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) Q_DECL_OVERRIDE { - FOREACH commandStart(identifier); +#define FOREACH for (std::vector::const_iterator it = mBuilders.begin(), end = mBuilders.end() ; it != end ; ++it) (*it)-> + void commandStart(const QString &identifier, int lineNumber) Q_DECL_OVERRIDE { + FOREACH commandStart(identifier, lineNumber); } - void commandEnd() Q_DECL_OVERRIDE { - FOREACH commandEnd(); + void commandEnd(int lineNumber) Q_DECL_OVERRIDE { + FOREACH commandEnd(lineNumber); } void testStart(const QString &identifier) Q_DECL_OVERRIDE { FOREACH testStart(identifier); } void testEnd() Q_DECL_OVERRIDE { FOREACH testEnd(); } void testListStart() Q_DECL_OVERRIDE { FOREACH testListStart(); } void testListEnd() Q_DECL_OVERRIDE { FOREACH testListEnd(); } - void blockStart() Q_DECL_OVERRIDE { - FOREACH blockStart(); + void blockStart(int lineNumber) Q_DECL_OVERRIDE { + FOREACH blockStart(lineNumber); } - void blockEnd() Q_DECL_OVERRIDE { - FOREACH blockEnd(); + void blockEnd(int lineNumber) Q_DECL_OVERRIDE { + FOREACH blockEnd(lineNumber); } void hashComment(const QString &comment) Q_DECL_OVERRIDE { FOREACH hashComment(comment); } void bracketComment(const QString &comment) Q_DECL_OVERRIDE { FOREACH bracketComment(comment); } void lineFeed() Q_DECL_OVERRIDE { FOREACH lineFeed(); } void error(const KSieve::Error &e) Q_DECL_OVERRIDE { FOREACH error(e); } void finished() Q_DECL_OVERRIDE { FOREACH finished(); } void taggedArgument(const QString &tag) Q_DECL_OVERRIDE { FOREACH taggedArgument(tag); } void stringArgument(const QString &string, bool multiline, const QString &fixme) Q_DECL_OVERRIDE { FOREACH stringArgument(string, multiline, fixme); } void numberArgument(unsigned long number, char quantifier) Q_DECL_OVERRIDE { FOREACH numberArgument(number, quantifier); } void stringListArgumentStart() Q_DECL_OVERRIDE { FOREACH stringListArgumentStart(); } void stringListEntry(const QString &string, bool multiline, const QString &fixme) Q_DECL_OVERRIDE { FOREACH stringListEntry(string, multiline, fixme); } void stringListArgumentEnd() Q_DECL_OVERRIDE { 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: 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; } qCDebug(LIBKSIEVE_LOG) << (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) { mResults[QString::fromLatin1(save_tag)] = string; } if (!found && !mRecursionGuard.count(mState)) { doProcess(method, string); } } - void commandStart(const QString &identifier) Q_DECL_OVERRIDE { + void commandStart(const QString &identifier, int lineNumber) Q_DECL_OVERRIDE { + Q_UNUSED(lineNumber) qCDebug(LIBKSIEVE_LOG); process(CommandStart, identifier); } - void commandEnd() Q_DECL_OVERRIDE { + void commandEnd(int lineNumber) Q_DECL_OVERRIDE { + Q_UNUSED(lineNumber) qCDebug(LIBKSIEVE_LOG); process(CommandEnd); } void testStart(const QString &identifier) Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(TestStart, identifier); } void testEnd() Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(TestEnd); } void testListStart() Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(TestListStart); } void testListEnd() Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(TestListEnd); } - void blockStart() Q_DECL_OVERRIDE { + void blockStart(int lineNumber) Q_DECL_OVERRIDE { + Q_UNUSED(lineNumber) qCDebug(LIBKSIEVE_LOG); process(BlockStart); ++mNestingDepth; } - void blockEnd() Q_DECL_OVERRIDE { + void blockEnd(int lineNumber) Q_DECL_OVERRIDE { + Q_UNUSED(lineNumber) qCDebug(LIBKSIEVE_LOG); --mNestingDepth; process(BlockEnd); } void hashComment(const QString &) Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); } void bracketComment(const QString &) Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); } void lineFeed() Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); } void error(const KSieve::Error &) Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); mState = 0; } void finished() Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); } void taggedArgument(const QString &tag) Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(TaggedArgument, tag); } void stringArgument(const QString &string, bool, const QString &) Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(StringArgument, string); } void numberArgument(unsigned long number, char) Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(NumberArgument, QString::number(number)); } void stringListArgumentStart() Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(StringListArgumentStart); } void stringListEntry(const QString &string, bool, const QString &) Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(StringListEntry, string); } void stringListArgumentEnd() Q_DECL_OVERRIDE { qCDebug(LIBKSIEVE_LOG); process(StringListArgumentEnd); } }; typedef GenericInformationExtractor GIE; static const GenericInformationExtractor::StateNode spamNodes[] = { - { 0, GIE::CommandStart, "if", 1, 0, Q_NULLPTR }, // 0 - { 0, GIE::TestStart, "header", 2, 0, Q_NULLPTR }, // 1 - { 0, GIE::TaggedArgument, "contains", 3, 0, Q_NULLPTR }, // 2 + { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0 + { 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, Q_NULLPTR, 5, 0, Q_NULLPTR }, // 4 - { 0, GIE::StringListEntry, "x-spam-flag", 6, 7, "x-spam-flag" }, // 5 - { 0, GIE::StringListEntry, Q_NULLPTR, 6, 8, Q_NULLPTR }, // 6 - { 0, GIE::StringListArgumentEnd, Q_NULLPTR, 0, 5, Q_NULLPTR }, // 7 - { 0, GIE::StringListArgumentEnd, Q_NULLPTR, 9, 0, Q_NULLPTR }, // 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, Q_NULLPTR, 11, 0, Q_NULLPTR }, // 10 - { 0, GIE::StringListEntry, "yes", 12, 13, "spam-flag-yes" }, // 11 - { 0, GIE::StringListEntry, Q_NULLPTR, 12, 14, Q_NULLPTR }, // 12 - { 0, GIE::StringListArgumentEnd, Q_NULLPTR, 0, 11, Q_NULLPTR }, // 13 - { 0, GIE::StringListArgumentEnd, Q_NULLPTR, 15, 0, Q_NULLPTR }, // 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, Q_NULLPTR, 16, 0, Q_NULLPTR }, // 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, Q_NULLPTR, 17, 0, Q_NULLPTR }, // 16 - { 1, GIE::CommandStart, "stop", 20, 19, "stop" }, // 17 - { -1, GIE::Any, Q_NULLPTR, 17, 0, Q_NULLPTR }, // 18 - { 0, GIE::BlockEnd, Q_NULLPTR, 0, 18, Q_NULLPTR }, // 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, Q_NULLPTR, 20, 20, Q_NULLPTR }, // 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(QStringLiteral("x-spam-flag")) && mResults.count(QStringLiteral("spam-flag-yes")) && - mResults.count(QStringLiteral("stop")); + mResults.count(QStringLiteral("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, Q_NULLPTR }, // 0 - { 0, GIE::TestStart, "not", 2, 0, Q_NULLPTR, }, // 1 - { 0, GIE::TestStart, "address", 3, 0, Q_NULLPTR }, // 2 + { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0 + { 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, Q_NULLPTR }, // 3 - { 0, GIE::TaggedArgument, "contains", 7, 0, Q_NULLPTR }, // 4 - { 0, GIE::TaggedArgument, "contains", 6, 0, Q_NULLPTR }, // 5 - { 0, GIE::TaggedArgument, "domain", 7, 0, Q_NULLPTR }, // 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, Q_NULLPTR, 9, 0, Q_NULLPTR }, // 8 - { 0, GIE::StringListEntry, "from", 10, 11, "from" }, // 9 - { 0, GIE::StringListEntry, Q_NULLPTR, 10, 12, Q_NULLPTR }, // 10 - { 0, GIE::StringListArgumentEnd, Q_NULLPTR, 0, 9, Q_NULLPTR }, // 11 - { 0, GIE::StringListArgumentEnd, Q_NULLPTR, 13, 0, Q_NULLPTR }, // 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, Q_NULLPTR, 17, 14, "domainName" }, // 13 - { 0, GIE::StringListArgumentStart, Q_NULLPTR, 15, 0, Q_NULLPTR }, // 14 - { 0, GIE::StringListEntry, Q_NULLPTR, 15, 16, "domainName" }, // 15 - { 0, GIE::StringListArgumentEnd, Q_NULLPTR, 17, 0, Q_NULLPTR }, // 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, Q_NULLPTR, 18, 0, Q_NULLPTR }, // 17 - { 0, GIE::TestEnd, Q_NULLPTR, 19, 0, Q_NULLPTR }, // 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, Q_NULLPTR, 20, 0, Q_NULLPTR }, // 19 - { 1, GIE::CommandStart, "stop", 23, 22, "stop" }, // 20 - { -1, GIE::Any, Q_NULLPTR, 20, 0, Q_NULLPTR }, // 21 - { 0, GIE::BlockEnd, Q_NULLPTR, 0, 21, Q_NULLPTR }, // 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, Q_NULLPTR, 23, 23, Q_NULLPTR } // 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(QStringLiteral("stop")) && mResults.count(QStringLiteral("from")) + return mResults.count(QStringLiteral("vacation")) && mResults.count(QStringLiteral("from")) ? mResults[QStringLiteral("domainName")] : QString(); } }; -// if not allof (currentDate :value "ge" date "YYYY-MM-DD", -// currentDate :value "le" date "YYYY-MM-DD") { keep; stop; } +// 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, Q_NULLPTR }, // 0 - { 0, GIE::TestStart, "not", 2, 0, Q_NULLPTR }, // 1 - { 0, GIE::TestStart, "allof", 3, 0, Q_NULLPTR }, // 2 + { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0 + { 0, GIE::TestStart, "allof", 2, 0, 0 }, // 1 // handle startDate and endDate in arbitrary order - { 0, GIE::TestListStart, Q_NULLPTR, 4, 0, Q_NULLPTR }, // 3 - { 0, GIE::TestStart, "currentdate", 5, 0, Q_NULLPTR }, // 4 - { 0, GIE::TaggedArgument, "value", 6, 0, Q_NULLPTR }, // 5 - { 0, GIE::StringArgument, "ge", 7, 9, Q_NULLPTR }, // 6 - { 0, GIE::StringArgument, "date", 8, 0, Q_NULLPTR }, // 7 - { 0, GIE::StringArgument, Q_NULLPTR, 12, 0, "startDate" }, // 8 - { 0, GIE::StringArgument, "le", 10, 0, Q_NULLPTR }, // 9 - { 0, GIE::StringArgument, "date", 11, 0, Q_NULLPTR }, // 10 - { 0, GIE::StringArgument, Q_NULLPTR, 12, 0, "endDate" }, // 11 - { 0, GIE::TestEnd, Q_NULLPTR, 13, 0, Q_NULLPTR }, // 12 - - { 0, GIE::TestStart, "currentdate", 14, 0, Q_NULLPTR }, // 13 - { 0, GIE::TaggedArgument, "value", 15, 0, Q_NULLPTR }, // 14 - { 0, GIE::StringArgument, "le", 16, 18, Q_NULLPTR }, // 15 - { 0, GIE::StringArgument, "date", 17, 0, Q_NULLPTR }, // 16 - { 0, GIE::StringArgument, Q_NULLPTR, 21, 0, "endDate" }, // 17 - { 0, GIE::StringArgument, "ge", 19, 0, Q_NULLPTR }, // 18 - { 0, GIE::StringArgument, "date", 20, 0, Q_NULLPTR }, // 19 - { 0, GIE::StringArgument, Q_NULLPTR, 21, 0, "startDate" }, // 20 - { 0, GIE::TestEnd, Q_NULLPTR, 22, 0, Q_NULLPTR }, // 21 - { 0, GIE::TestListEnd, Q_NULLPTR, 23, 0, Q_NULLPTR }, // 22 - - { 0, GIE::TestEnd, Q_NULLPTR, 24, 0, Q_NULLPTR }, // 23 - { 0, GIE::TestEnd, Q_NULLPTR, 25, 0, Q_NULLPTR }, // 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, 10, 0 }, // 5 + { 0, GIE::StringArgument, "date", 7, 8, 0 }, // 6 + { 0, GIE::StringArgument, 0, 15, 0, "startDate" }, // 7 + { 0, GIE::StringArgument, "iso8601", 9, 0, 0 }, // 8 + { 0, GIE::StringArgument, 0, 15, 0, "startDateTime" },// 9 + { 0, GIE::StringArgument, "le", 11, 0, 0 }, // 10 + { 0, GIE::StringArgument, "date", 12, 13, 0 }, // 11 + { 0, GIE::StringArgument, 0, 15, 0, "endDate" }, // 12 + { 0, GIE::StringArgument, "iso8601", 14, 0, 0 }, // 13 + { 0, GIE::StringArgument, 0, 15, 0, "endDateTime" }, // 14 + { 0, GIE::TestEnd, 0, 16, 0, 0 }, // 15 + + { 0, GIE::TestStart, "currentdate", 17, 16, 0 }, // 16 + { 0, GIE::TaggedArgument, "value", 18, 17, 0 }, // 17 + { 0, GIE::StringArgument, "le", 19, 23, 0 }, // 18 + { 0, GIE::StringArgument, "date", 20, 21, 0 }, // 19 + { 0, GIE::StringArgument, 0, 28, 0, "endDate" }, // 20 + { 0, GIE::StringArgument, "iso8601", 22, 0, 0 }, // 21 + { 0, GIE::StringArgument, 0, 28, 0, "endDateTime" }, // 22 + { 0, GIE::StringArgument, "ge", 24, 0, 0 }, // 23 + { 0, GIE::StringArgument, "date", 25, 26, 0 }, // 24 + { 0, GIE::StringArgument, 0, 28, 0, "startDate" }, // 25 + { 0, GIE::StringArgument, "iso8601", 27, 0, 0 }, // 26 + { 0, GIE::StringArgument, 0, 28, 0, "startDateTime" }, // 27 + { 0, GIE::TestEnd, 0, 32, 0, 0 }, // 28 + { 0, GIE::TestStart, 0, 31, 30, 0 }, // 29 + { -1, GIE::Any, 0, 32, 0, 0 }, // 30 + { 0, GIE::TestEnd, 0, 32, 30, 0 }, // 31 + { 0, GIE::TestListEnd, 0, 33, 29, 0 }, // 32 + + { 0, GIE::TestEnd, 0, 34, 0, 0 }, // 33 // block of commands, find "stop", take nested if's into account: - { 0, GIE::BlockStart, Q_NULLPTR, 26, 0, Q_NULLPTR }, // 25 - { 1, GIE::CommandStart, "stop", 29, 28, "stop" }, // 26 - { -1, GIE::Any, Q_NULLPTR, 26, 0, Q_NULLPTR }, // 27 - { 0, GIE::BlockEnd, Q_NULLPTR, 0, 27, Q_NULLPTR }, // 28 + { 0, GIE::BlockStart, 0, 36, 33, 0 }, // 34 + { -1, GIE::Any, 0, 36, 0, 0 }, // 35 + { 1, GIE::CommandStart, "vacation", 38, 35, "vacation" }, // 36 + { -1, GIE::Any, 0, 38, 0, 0 }, // 37 + { 0, GIE::BlockEnd, 0, 39, 37, 0 }, // 38 - { -1, GIE::Any, Q_NULLPTR, 27, 27, Q_NULLPTR } // 29 end state + { -1, GIE::Any, 0, 39, 39, 0 } // 39 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() const { - return date(QStringLiteral("endDate")); + if (results().count(QStringLiteral("endDateTime")) == 1) { + return datetime(QStringLiteral("endDateTime")).date(); + } else { + return date(QStringLiteral("endDate")); + } } QDate startDate() const { - return date(QStringLiteral("startDate")); + if (results().count(QStringLiteral("startDateTime")) == 1) { + return datetime(QStringLiteral("startDateTime")).date(); + } else { + return date(QStringLiteral("startDate")); + } + } + + QTime endTime() const + { + return datetime(QStringLiteral("endDateTime")).time(); + } + + QTime startTime() const + { + return datetime(QStringLiteral("startDateTime")).time(); } private: QDate date(const QString &name) const { - if (mResults.count(name) == 0) { + if (results().count(name) == 0) { return QDate(); } else { - return QDate::fromString(mResults.at(name), Qt::ISODate); + return QDate::fromString(results().at(name), Qt::ISODate); + } + } + + QDateTime datetime(const QString &name) const + { + if (results().count(name) == 0) { + return QDateTime(); + } else { + return QDateTime::fromString(results().at(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, + RedirectCommand }; 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; + } + VacationUtils::MailAction mailAction() const + { + return mMailAction; + } + const QString &mailActionRecipient() const + { + return mMailActionRecipient; + } const QString &subject() const { return mSubject; } + int lineStart() const + { + return mLineStart; + } + + int lineEnd() const + { + return mLineEnd; + } + private: - void commandStart(const QString &identifier) Q_DECL_OVERRIDE; + void commandStart(const QString &identifier, int lineNumber) Q_DECL_OVERRIDE; - void commandEnd() Q_DECL_OVERRIDE; + void commandEnd(int lineNumber) Q_DECL_OVERRIDE; - void testStart(const QString &) Q_DECL_OVERRIDE {} + void testStart(const QString &) Q_DECL_OVERRIDE; void testEnd() Q_DECL_OVERRIDE {} void testListStart() Q_DECL_OVERRIDE {} void testListEnd() Q_DECL_OVERRIDE {} - void blockStart() Q_DECL_OVERRIDE {} - void blockEnd() Q_DECL_OVERRIDE {} - void hashComment(const QString &) Q_DECL_OVERRIDE {} + void blockStart(int lineNumber) Q_DECL_OVERRIDE; + void blockEnd(int lineNumber) Q_DECL_OVERRIDE; + void hashComment(const QString &) Q_DECL_OVERRIDE; void bracketComment(const QString &) Q_DECL_OVERRIDE {} void lineFeed() Q_DECL_OVERRIDE {} void error(const KSieve::Error &e) Q_DECL_OVERRIDE; void finished() Q_DECL_OVERRIDE; void taggedArgument(const QString &tag) Q_DECL_OVERRIDE; void stringArgument(const QString &string, bool, const QString &) Q_DECL_OVERRIDE; void numberArgument(unsigned long number, char) Q_DECL_OVERRIDE; void stringListArgumentStart() Q_DECL_OVERRIDE; void stringListEntry(const QString &string, bool, const QString &) Q_DECL_OVERRIDE; void stringListArgumentEnd() Q_DECL_OVERRIDE; private: Context mContext; int mNotificationInterval; QString mMessageText; QString mSubject; QStringList mAliases; + bool mActive; + bool mInIfBlock; + bool mFoundInBlock; + int mBlockLevel; + QString mIfComment; + int mLineStart; + int mLineEnd; + + VacationUtils::MailAction mMailAction; + Context mMailActionContext; + QString mMailActionRecipient; 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) + { + Q_UNUSED(lineNumber) + } + void blockEnd(int lineNumber) + { + Q_UNUSED(lineNumber) + } + void hashComment(const QString &) {} + void bracketComment(const QString &) {} + void lineFeed() {} + void error(const KSieve::Error &e); + void finished(); + + void taggedArgument(const QString &tag) + { + Q_UNUSED(tag) + } + void numberArgument(unsigned long number, char) + { + Q_UNUSED(number) + } + + 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/src/ksieveui/vacation/vacationutils.h b/libksieve/src/ksieveui/vacation/vacationutils.h index 1cd8e101c3..15cf0b92c3 100644 --- a/libksieve/src/ksieveui/vacation/vacationutils.h +++ b/libksieve/src/ksieveui/vacation/vacationutils.h @@ -1,62 +1,70 @@ /* Copyright (c) 2013-2015 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 class QDate; namespace KMime { namespace Types { struct AddrSpec; typedef QVector AddrSpecList; } } namespace KSieveUi { namespace VacationUtils { + +enum MailAction { + Keep, + Discard, + Sendto, + CopyTo, +}; + QString defaultMessageText(); QString defaultSubject(); int defaultNotificationInterval(); QStringList defaultMailAliases(); bool defaultSendForSpam(); QString defaultDomainName(); QDate defaultStartDate(); QDate defaultEndDate(); QString composeScript(const QString &messageText, 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, QString &subject, int ¬ificationInterval, QStringList &aliases, bool &sendForSpam, QString &domainName, QDate &startDate, QDate &endDate); } } #endif // VACATIONUTILS_H diff --git a/libksieve/src/parser/parser.cpp b/libksieve/src/parser/parser.cpp index 0bcf16f753..050d3659f5 100644 --- a/libksieve/src/parser/parser.cpp +++ b/libksieve/src/parser/parser.cpp @@ -1,717 +1,717 @@ /* -*- 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 namespace KSieve { // // // Parser Bridge implementation // // Parser::Parser(const char *scursor, const char *const send, int options) : i(Q_NULLPTR) { i = new Impl(scursor, send, options); } Parser::~Parser() { delete i; i = Q_NULLPTR; } 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(Q_NULLPTR) { } 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()); + 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(); + 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().at(0).toLatin1()); switch (tokenValue().at(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().at(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] - '0'; 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