/**
 * 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 "xmlkioentitiesloader.h"
#include "parserprivate.h"

#include <KUrl>
#include <KIO/Scheduler>
#include <KDebug>

extern "C" {
    #include <libxml/parserInternals.h>
    #include <libexslt/exslt.h>
    #include <libxslt/transform.h>
}

xmlExternalEntityLoader defaultLoader = NULL;

const xmlChar *kurlToXmlChar(const KUrl &url)
{
    return (const xmlChar*)(url.toEncoded().constData());
}

xmlParserInputPtr xmlKioEntitiesLoader(const char *URL, const char *ID, xmlParserCtxtPtr ctxt)
{
    // if not loaded (or uninstalled), just run defaultLoader
    if (!KioEntityLoader::isLoaded()) {
        return defaultLoader(URL, ID, ctxt);
    }

    KUrl docUrl;
    if (ctxt->myDoc && ctxt->node) {
        KUrl baseUrl(ctxt->myDoc->name);
        KUrl tmpUrl((const char*)URL);
        docUrl = KUrl(baseUrl, tmpUrl.toEncoded());
    } else {
        docUrl = KUrl((const char*)URL);
    }

    // check if loading the document is allowed in current context
    if (!ParserPrivate::securityLoadCheck(docUrl, ctxt)) {
        ParserPrivate::contextAddSecurityError(ctxt, docUrl);
        return 0;
    }

    xmlParserInputPtr ret;

    // try loading from local/catalog first
    ret = xmlNoNetExternalEntityLoader(URL, ID, ctxt);
    if (ret == 0) {
        KioEntityLoader request(docUrl);
        QByteArray data = request.run();
        if (!data.isEmpty()) {
            xmlParserInputBufferPtr buffer;
            buffer = xmlParserInputBufferCreateMem(data.constData(), data.size(),
                                                XML_CHAR_ENCODING_UTF8);
            ret = xmlNewIOInputStream(ctxt, buffer, XML_CHAR_ENCODING_UTF8);
        }
    }

    if (ret == 0) {
        ParserPrivate::contextAddNotFoundError(ctxt, docUrl);
    }

    return ret;
}

xmlDocPtr xsltKioDocLoader(const xmlChar *URI, xmlDictPtr dict, int options,
                           void *ctxt, xsltLoadType type)
{
    if (!KioEntityLoader::isLoaded()) {
        return 0;
    }

    xsltTransformContextPtr tctxt = 0;
    xsltStylesheetPtr curstyle = 0;
    if (type == XSLT_LOAD_DOCUMENT) {
        tctxt = (xsltTransformContextPtr)ctxt;
        if (tctxt->style != 0) {
            curstyle = tctxt->style;
        } else {
            return 0;
        }
    } else if (type == XSLT_LOAD_STYLESHEET) {
        curstyle = (xsltStylesheetPtr)ctxt;
    } else {
        return 0; // shouldn't happen
    }
    
    // figure out if security allows us to load this style.
    // we use the URL of the document requesting the style to
    // check.
    if (!curstyle || !curstyle->doc) {
        return 0;
    }
    KUrl parentUrl((const char*)curstyle->doc->URL);
    KUrl url((const char*)URI);
    if (parentUrl.hasHost() && !url.hasHost()) {
        if (tctxt != 0) {
            ParserPrivate::contextAddSecurityError(tctxt, url);
        } else {
            ParserPrivate::docAddSecurityError(curstyle->doc, url);
        }
        return 0;
    }
    
    xmlParserCtxtPtr pctxt;
    pctxt = ParserPrivate::createParserContext(!url.hasHost(), dict, options);
    if (pctxt == 0) {
        return 0;
    }
    xmlParserInputPtr inputStream = xmlLoadExternalEntity((const char*)URI, 0, pctxt);
    if (inputStream == 0) {
        if (tctxt) {
            ParserPrivate::contextAddErrors(tctxt,
                                            ParserPrivate::contextGetErrors(pctxt));
        } else {
            ParserPrivate::docAddErrors(curstyle->doc,
                                        ParserPrivate::contextGetErrors(pctxt));
        }
        ParserPrivate::freeParserContext(pctxt);
        return 0;
    }

    inputPush(pctxt, inputStream);
    if (pctxt->directory == 0) {
        pctxt->directory = xmlParserGetDirectory((const char*)URI);
    }

    xmlParseDocument(pctxt);

    return ParserPrivate::freeParserContext(pctxt);
}

KioEntityLoaderInstaller::KioEntityLoaderInstaller()
{
    KioEntityLoader::installEntityLoader();
}

KioEntityLoaderInstaller::~KioEntityLoaderInstaller()
{
    KioEntityLoader::uninstallEntityLoader();
}

bool KioEntityLoader::s_isLoaded = false;

KioEntityLoader::KioEntityLoader(const KUrl &url)
    : m_url(url), m_errorCode(0)
{
}

KioEntityLoader::~KioEntityLoader()
{
}

QByteArray KioEntityLoader::run()
{
    KIO::TransferJob *job = KIO::get(m_url, KIO::NoReload, KIO::HideProgressInfo);
    connect(job, SIGNAL(data(KIO::Job*, const QByteArray&)),
            this, SLOT(slotData(KIO::Job*, const QByteArray&)));
    connect(job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*)));

    KIO::Scheduler::scheduleJob(job);

    // process until the job is finished
    m_waitLoop.exec();

    if (m_errorCode == 0) {
        return m_data;
    } else {
        return QByteArray();
    }
}

bool KioEntityLoader::isLoaded()
{
    return s_isLoaded;
}

void KioEntityLoader::installEntityLoader()
{
    if (!s_isLoaded) {
        kDebug() << "Installing xmlKioEntitiesLoader";
        // entity loader might already have been set
        if (defaultLoader == NULL) {
            defaultLoader = xmlGetExternalEntityLoader();
            xmlSetExternalEntityLoader(xmlKioEntitiesLoader);
        }
        xsltSetLoaderFunc(xsltKioDocLoader);
        exsltRegisterAll();
        s_isLoaded = true;
    }
}

void KioEntityLoader::uninstallEntityLoader()
{
    xsltSetLoaderFunc(0);
    s_isLoaded = false;
}

void KioEntityLoader::slotData(KIO::Job *, const QByteArray &data)
{
    m_data.append(data);
}

void KioEntityLoader::slotResult(KJob *job)
{
    m_errorCode = job->error();
    m_waitLoop.quit();
}

#include "xmlkioentitiesloader.moc"
