/**
 * Copyright 2009  Michael Leupold <lemma@confuego.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) version 3, or any
 * later version accepted by the membership of KDE e.V. (or its
 * successor approved by the membership of KDE e.V.), which shall
 * act as a proxy defined in Section 6 of version 3 of the license.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "xsltsecuritytest.h"
#include <QtTest/QTest>
#include <KAboutData>
#include <KComponentData>
#include <KUrl>
#include <KIO/NetAccess>
#include <QtCore/QFile>
#include <QtXml/QDomDocument>
#include <xmlkioentitiesloader.h>
#include <xsltransformation.h>

#include <KDebug>

#include "test_config.h"

// determine the <?xml-stylesheet?> href from xml inputData
KUrl findStyleSheetUrl(const QString &inputData)
{
    QDomDocument doc("document");
    if (!doc.setContent(inputData)) {
        return KUrl();
    }

    QRegExp rxType("^\\s*type\\s*\\=\\s*\\\"text/xsl\\\"\\s*$", Qt::CaseInsensitive);
    QRegExp rxHref("^\\s*href\\s*\\=\\s*\\\"([^\\\"]*)\\\"\\s*$", Qt::CaseInsensitive);
    bool isXsl = false;
    QString styleUrl;
    for (int i = 0; i < doc.childNodes().count(); ++i) {
        QDomNode node(doc.childNodes().item(i));
        if (node.isProcessingInstruction()) {
            QString data(node.toProcessingInstruction().data());
            QList<QString> attributes(data.split(QRegExp("\\s"), QString::SkipEmptyParts));
            Q_FOREACH(const QString &attribute, attributes) {
                if (rxType.exactMatch(attribute)) {
                    isXsl = true;
                } else if (rxHref.exactMatch(attribute)) {
                    styleUrl = rxHref.cap(1);
                }
            }
            if (isXsl && !styleUrl.isEmpty()) {
                break;
            }
            isXsl = false;
            styleUrl.clear();
        }
    }

    return KUrl(styleUrl);
}

// load any url, save it to a temporary file and return the contents
QString loadFile(const KUrl fileName)
{
    QString tmpFile;
    QString ret;
    if (KIO::NetAccess::download(fileName, tmpFile, 0)) {
        QFile file(tmpFile);
        if (file.open(QIODevice::ReadOnly)) {
            ret = file.readAll();
        }
        file.close();
        KIO::NetAccess::removeTempFile(tmpFile);
    }
    return ret;
}

void XsltSecurityTest::initTestCase()
{
    KAboutData aboutData("xsltsecuritytest", "xsltsecuritytest",
                         ki18n("DO_NOT_TRANSLATE"), "0.0");
    KComponentData componentData(&aboutData);

    // start the test mini-http server
//     m_httpServer.start(PERL_EXECUTABLE, QStringList() << "testhttpserver.pl");
//     QVERIFY(m_httpServer.waitForStarted());

    KioEntityLoader::installEntityLoader();
}

class TestStruct
{
public:
    TestStruct(const QString &u, const char *d, bool e)
        : url(u), description(d), expect_result(e) {}
    QString url;
    const char *description;
    bool expect_result;
};

void XsltSecurityTest::importTest()
{
    QList<TestStruct> testStructs;
    testStructs << TestStruct(QString("%1/security-local.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading local stylesheet", true)
                << TestStruct(QString("%1/security-remote.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading remote stylesheet", true)
                << TestStruct(QString("%1/security-local.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading local stylesheet", false)
                << TestStruct(QString("%1/security-remote.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading remote stylesheet", true)
                << TestStruct(QString("%1/security-local-local.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading local stylesheet importing local stylesheet",
                              true)
                << TestStruct(QString("%1/security-local-remote.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading local stylesheet importing remote stylesheet",
                              true)
                << TestStruct(QString("%1/security-remote-local.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading remote stylesheet importing local stylesheet",
                              false)
                << TestStruct(QString("%1/security-remote-remote.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading remote stylesheet importing remote stylesheet",
                              true)
                << TestStruct(QString("%1/security-local-local.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading local stylesheet importing local stylesheet",
                              false)
                << TestStruct(QString("%1/security-local-remote.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading local stylesheet importing remote stylesheet",
                              false)
                << TestStruct(QString("%1/security-remote-local.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading remote stylesheet importing local stylesheet",
                              false)
                << TestStruct(QString("%1/security-remote-remote.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading remote stylesheet importing remote stylesheet",
                              true);

    Q_FOREACH(const TestStruct &curtest, testStructs) {
        KUrl url(curtest.url); // FIXME: valgrind reports a memleak here.
        XslTransformation transformation;
        
        // load input file
        QString inputData = loadFile(url);
        QVERIFY2(!inputData.isEmpty(), curtest.description);

        // figure out the stylesheet to use
        KUrl styleUrl = findStyleSheetUrl(inputData);
        QVERIFY2(!styleUrl.isEmpty(), curtest.description);

        if (styleUrl.isRelative()) {
            KUrl newStyleUrl = url;
            newStyleUrl.setFileName(styleUrl.toEncoded());
            styleUrl = newStyleUrl;
        }

        // set the input data
        QVERIFY2(transformation.setInput(url, inputData), curtest.description);
        // load the stylesheet
        QVERIFY2(transformation.loadStylesheet(styleUrl) == curtest.expect_result,
                 curtest.description);
        if (curtest.expect_result) {
            // do the transformation
            QVERIFY2(transformation.transform(), curtest.description);
        }
    }
}

void XsltSecurityTest::entityTest()
{
    QList<TestStruct> testStructs;
    testStructs << TestStruct(QString("%1/security-entity-local.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading local entity", true)
                << TestStruct(QString("%1/security-entity-remote.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading remote entity", true)
                << TestStruct(QString("%1/security-entity-local.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading local entity", false)
                << TestStruct(QString("%1/security-entity-remote.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading remote entity", true)
                << TestStruct(QString("%1/security-entity-dtd-local.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading local dtd loading local entity", true)
                << TestStruct(QString("%1/security-entity-dtd-remote.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading remote dtd loading local entity", false)
                << TestStruct(QString("%1/security-entity-dtd-local.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading local dtd loading local entity", false)
                << TestStruct(QString("%1/security-entity-dtd-remote.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading remote dtd loading local entity", false);

    Q_FOREACH(const TestStruct &curtest, testStructs) {
        KUrl url(curtest.url);
        XslTransformation transformation;

        // load input file
        QString inputData = loadFile(url);
        QVERIFY2(!inputData.isEmpty(), curtest.description);

        // figure out the stylesheet to use
        KUrl styleUrl = findStyleSheetUrl(inputData);
        QVERIFY2(!styleUrl.isEmpty(), curtest.description);

        if (styleUrl.isRelative()) {
            KUrl newStyleUrl = url;
            newStyleUrl.setFileName(styleUrl.toEncoded());
            styleUrl = newStyleUrl;
        }

        // We'll keep it simple: if the transformation output contains the word
        // "SECRET" the external entity got included and something is broken.
        bool rc = transformation.setInput(url, inputData);
        if (rc) {
            // load the stylesheet
            transformation.loadStylesheet(styleUrl);
            // do the transformation
            transformation.transform();
            QVERIFY2(transformation.output().contains("SECRET") == curtest.expect_result,
                     curtest.description);
        } else {
            QVERIFY2(!curtest.expect_result, curtest.description);
        }
    }
}

void XsltSecurityTest::xslEntityTest()
{
    QList<TestStruct> testStructs;
    testStructs << TestStruct(QString("%1/security-entity-xsl-local-local.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading local style loading local entity", true)
                << TestStruct(QString("%1/security-entity-xsl-local-remote.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading local style loading remote entity", true)
                << TestStruct(QString("%1/security-entity-xsl-remote-local.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading remote style loading local entity", false)
                << TestStruct(QString("%1/security-entity-xsl-remote-remote.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading remote style loading remote entity", true)
                << TestStruct(QString("%1/security-entity-xsl-local-local.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading local style loading local entity", false)
                << TestStruct(QString("%1/security-entity-xsl-local-remote.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading local style loading remote entity", false)
                << TestStruct(QString("%1/security-entity-xsl-remote-local.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading remote style loading local entity", false)
                << TestStruct(QString("%1/security-entity-xsl-remote-remote.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading remote style loading remote entity", true);

    Q_FOREACH(const TestStruct &curtest, testStructs) {
        KUrl url(curtest.url);
        XslTransformation transformation;

        // load input file
        QString inputData = loadFile(url);
        QVERIFY2(!inputData.isEmpty(), curtest.description);

        // figure out the stylesheet to use
        KUrl styleUrl = findStyleSheetUrl(inputData);
        QVERIFY2(!styleUrl.isEmpty(), curtest.description);

        if (styleUrl.isRelative()) {
            KUrl newStyleUrl = url;
            newStyleUrl.setFileName(styleUrl.toEncoded());
            styleUrl = newStyleUrl;
        }

        // We'll keep it simple: if the transformation output contains the word
        // "SECRET" the external entity got included and something is broken.
        bool rc = transformation.setInput(url, inputData);
        if (rc) {
            // load the stylesheet
            rc = transformation.loadStylesheet(styleUrl);
            if (rc) {
                // do the transformation
                transformation.transform();
                QVERIFY2(transformation.output().contains("SECRET") == curtest.expect_result,
                        curtest.description);
            } else {
                QVERIFY2(!curtest.expect_result, curtest.description);
            }
        } else {
            QVERIFY2(!curtest.expect_result, curtest.description);
        }
    }
}

void XsltSecurityTest::fnDocumentTest()
{
    QList<TestStruct> testStructs;
    testStructs << TestStruct(QString("%1/security-fndocument-local-local.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading local stylesheet accessing local document", true)
                << TestStruct(QString("%1/security-fndocument-local-remote.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading local stylesheet accessing remote document", true)
                << TestStruct(QString("%1/security-fndocument-remote-local.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading remote stylesheet accessing local document", false)
                << TestStruct(QString("%1/security-fndocument-remote-remote.xml").arg(LOCAL_BASE_URL),
                              "Local XML loading remote stylesheet accessing remote document", true)
                << TestStruct(QString("%1/security-fndocument-local-local.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading local stylesheet accessing local document", false)
                << TestStruct(QString("%1/security-fndocument-local-remote.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading local stylesheet accessing remote document", false)
                << TestStruct(QString("%1/security-fndocument-remote-local.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading remote stylesheet accessing local document", false)
                << TestStruct(QString("%1/security-fndocument-remote-remote.xml").arg(REMOTE_BASE_URL),
                              "Remote XML loading remote stylesheet accessing remote document", true);

    Q_FOREACH(const TestStruct &curtest, testStructs) {
        KUrl url(curtest.url);
        XslTransformation transformation;

        // load input file
        QString inputData = loadFile(url);
        QVERIFY2(!inputData.isEmpty(), curtest.description);

        // figure out the stylesheet to use
        KUrl styleUrl = findStyleSheetUrl(inputData);
        QVERIFY2(!styleUrl.isEmpty(), curtest.description);

        if (styleUrl.isRelative()) {
            KUrl newStyleUrl = url;
            newStyleUrl.setFileName(styleUrl.toEncoded());
            styleUrl = newStyleUrl;
        }

        bool rc = transformation.setInput(url, inputData);
        if (rc) {
            // load the stylesheet
            rc = transformation.loadStylesheet(styleUrl);
            if (rc) {
                // do the transformation
                transformation.transform();
                QVERIFY2(transformation.output().contains("SECRET") == curtest.expect_result,
                         curtest.description);
            } else {
                QVERIFY2(!curtest.expect_result, curtest.description);
            }
        } else {
            QVERIFY2(!curtest.expect_result, curtest.description);
        }
    }
}

void XsltSecurityTest::cleanupTestCase()
{
    xsltCleanupGlobals();
    xmlCleanupParser();

//     m_httpServer.close();
}

QTEST_MAIN(XsltSecurityTest);
#include "xsltsecuritytest.moc"
