/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <xmlsignaturehelper.hxx>
#include <documentsignaturehelper.hxx>
#include <xsecctl.hxx>

#include <xmlsignaturehelper2.hxx>

#include <tools/stream.hxx>
#include <tools/datetime.hxx>

#include <xmloff/attrlist.hxx>

#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/io/XActiveDataSource.hpp>
#include <com/sun/star/io/XTruncate.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/StringPair.hpp>
#include <com/sun/star/xml/sax/Parser.hpp>
#include <com/sun/star/xml/sax/Writer.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/embed/XStorage.hpp>
#include <com/sun/star/embed/StorageFormats.hpp>
#include <com/sun/star/embed/XTransactedObject.hpp>
#include <com/sun/star/io/XSeekable.hpp>

#include <comphelper/ofopxmlhelper.hxx>
#include <comphelper/sequence.hxx>
#include <tools/diagnose_ex.h>
#include <sal/log.hxx>

#define NS_DOCUMENTSIGNATURES   "http://openoffice.org/2004/documentsignatures"
#define NS_DOCUMENTSIGNATURES_ODF_1_2 "urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0"
#define OOXML_SIGNATURE_ORIGIN "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin"
#define OOXML_SIGNATURE_SIGNATURE "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature"

using namespace ::com::sun::star;
using namespace ::com::sun::star::graphic;
using namespace ::com::sun::star::uno;

XMLSignatureHelper::XMLSignatureHelper( const uno::Reference< uno::XComponentContext >& rxCtx)
    : mxCtx(rxCtx), mbODFPre1_2(false)
{
    mpXSecController = new XSecController(rxCtx);
    mbError = false;
}

XMLSignatureHelper::~XMLSignatureHelper()
{
}

void XMLSignatureHelper::SetStorage(
    const Reference < css::embed::XStorage >& rxStorage,
    const OUString& sODFVersion)
{
    SAL_WARN_IF( mxUriBinding.is(), "xmlsecurity.helper", "SetStorage - UriBinding already set!" );
    mxUriBinding = new UriBindingHelper( rxStorage );
    SAL_WARN_IF(!rxStorage.is(), "xmlsecurity.helper", "SetStorage - empty storage!");
    mbODFPre1_2 = DocumentSignatureHelper::isODFPre_1_2(sODFVersion);
}


void XMLSignatureHelper::SetStartVerifySignatureHdl( const Link<LinkParamNone*,bool>& rLink )
{
    maStartVerifySignatureHdl = rLink;
}


void XMLSignatureHelper::StartMission(const uno::Reference<xml::crypto::XXMLSecurityContext>& xSecurityContext)
{
    if ( !mxUriBinding.is() )
        mxUriBinding = new UriBindingHelper();

    mpXSecController->startMission(mxUriBinding, xSecurityContext);
}

void XMLSignatureHelper::EndMission()
{
    mpXSecController->endMission();
}

sal_Int32 XMLSignatureHelper::GetNewSecurityId()
{
    return mpXSecController->getNewSecurityId();
}

void XMLSignatureHelper::SetX509Certificate(
        sal_Int32 nSecurityId,
        const OUString& ouX509IssuerName,
        const OUString& ouX509SerialNumber,
        const OUString& ouX509Cert,
        const OUString& ouX509CertDigest,
        svl::crypto::SignatureMethodAlgorithm eAlgorithmID)
{
    mpXSecController->setX509Certificate(
        nSecurityId,
        ouX509IssuerName,
        ouX509SerialNumber,
        ouX509Cert,
        ouX509CertDigest,
        eAlgorithmID);
}

void XMLSignatureHelper::AddEncapsulatedX509Certificate(const OUString& ouEncapsulatedX509Certificate)
{
    mpXSecController->addEncapsulatedX509Certificate(ouEncapsulatedX509Certificate);
}

void XMLSignatureHelper::SetGpgCertificate(sal_Int32 nSecurityId,
                                           const OUString& ouGpgCertDigest,
                                           const OUString& ouGpgCert,
                                           const OUString& ouGpgOwner)
{
    mpXSecController->setGpgCertificate(
        nSecurityId,
        ouGpgCertDigest,
        ouGpgCert,
        ouGpgOwner);
}

void XMLSignatureHelper::SetDateTime( sal_Int32 nSecurityId, const ::DateTime& rDateTime )
{
    css::util::DateTime stDateTime = rDateTime.GetUNODateTime();
    mpXSecController->setDate( nSecurityId, stDateTime );
}

void XMLSignatureHelper::SetDescription(sal_Int32 nSecurityId, const OUString& rDescription)
{
    mpXSecController->setDescription(nSecurityId, rDescription);
}

void XMLSignatureHelper::SetSignatureLineId(sal_Int32 nSecurityId, const OUString& rSignatureLineId)
{
    mpXSecController->setSignatureLineId(nSecurityId, rSignatureLineId);
}

void XMLSignatureHelper::SetSignatureLineValidGraphic(
    sal_Int32 nSecurityId, const css::uno::Reference<XGraphic>& xValidGraphic)
{
    mpXSecController->setSignatureLineValidGraphic(nSecurityId, xValidGraphic);
}

void XMLSignatureHelper::SetSignatureLineInvalidGraphic(
    sal_Int32 nSecurityId, const css::uno::Reference<XGraphic>& xInvalidGraphic)
{
    mpXSecController->setSignatureLineInvalidGraphic(nSecurityId, xInvalidGraphic);
}

void XMLSignatureHelper::AddForSigning( sal_Int32 nSecurityId, const OUString& uri, bool bBinary, bool bXAdESCompliantIfODF )
{
    mpXSecController->signAStream( nSecurityId, uri, bBinary, bXAdESCompliantIfODF );
}


uno::Reference<xml::sax::XWriter> XMLSignatureHelper::CreateDocumentHandlerWithHeader(
    const css::uno::Reference< css::io::XOutputStream >& xOutputStream )
{
    /*
     * get SAX writer component
     */
    uno::Reference< xml::sax::XWriter > xSaxWriter = xml::sax::Writer::create(mxCtx);

    /*
     * connect XML writer to output stream
     */
    xSaxWriter->setOutputStream( xOutputStream );

    /*
     * write the xml context for signatures
     */
    SvXMLAttributeList *pAttributeList = new SvXMLAttributeList();
    OUString sNamespace;
    if (mbODFPre1_2)
        sNamespace = NS_DOCUMENTSIGNATURES;
    else
        sNamespace = NS_DOCUMENTSIGNATURES_ODF_1_2;

    pAttributeList->AddAttribute(
        "xmlns",
        sNamespace);

    xSaxWriter->startDocument();
    xSaxWriter->startElement(
        "document-signatures",
        uno::Reference< css::xml::sax::XAttributeList > (pAttributeList));

    return xSaxWriter;
}

void XMLSignatureHelper::CloseDocumentHandler( const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler )
{
    xDocumentHandler->endElement( "document-signatures" );
    xDocumentHandler->endDocument();
}

void XMLSignatureHelper::ExportSignature(
    const uno::Reference< xml::sax::XDocumentHandler >& xDocumentHandler,
    const SignatureInformation& signatureInfo,
    bool bXAdESCompliantIfODF )
{
    XSecController::exportSignature(xDocumentHandler, signatureInfo, bXAdESCompliantIfODF);
}

void XMLSignatureHelper::ExportOOXMLSignature(const uno::Reference<embed::XStorage>& xRootStorage, const uno::Reference<embed::XStorage>& xSignatureStorage, const SignatureInformation& rInformation, int nSignatureIndex)
{
    uno::Reference<io::XOutputStream> xOutputStream(xSignatureStorage->openStreamElement("sig" + OUString::number(nSignatureIndex) + ".xml", embed::ElementModes::READWRITE), uno::UNO_QUERY);

    if (rInformation.aSignatureBytes.hasElements())
        // This is a signature roundtrip, just write back the signature as-is.
        xOutputStream->writeBytes(rInformation.aSignatureBytes);
    else
    {
        uno::Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(mxCtx);
        xSaxWriter->setOutputStream(xOutputStream);
        xSaxWriter->startDocument();

        uno::Reference<xml::sax::XDocumentHandler> xDocumentHandler(xSaxWriter, uno::UNO_QUERY);
        mpXSecController->exportOOXMLSignature(xRootStorage, xDocumentHandler, rInformation);

        xSaxWriter->endDocument();
    }
}

void XMLSignatureHelper::CreateAndWriteSignature( const uno::Reference< xml::sax::XDocumentHandler >& xDocumentHandler, bool bXAdESCompliantIfODF )
{
    mbError = false;

    if ( !mpXSecController->WriteSignature( xDocumentHandler, bXAdESCompliantIfODF ) )
    {
        mbError = true;
    }
}

bool XMLSignatureHelper::ReadAndVerifySignature( const css::uno::Reference< css::io::XInputStream >& xInputStream )
{
    mbError = false;

    SAL_WARN_IF(!xInputStream.is(), "xmlsecurity.helper", "input stream missing");

    // prepare ParserInputSrouce
    xml::sax::InputSource aParserInput;
    aParserInput.aInputStream = xInputStream;

    // get SAX parser component
    uno::Reference< xml::sax::XParser > xParser = xml::sax::Parser::create(mxCtx);

    // create a signature reader
    uno::Reference< xml::sax::XDocumentHandler > xHandler
        = mpXSecController->createSignatureReader(*this);

    // setup the connection:
    // Parser -> SignatureReader
    xParser->setDocumentHandler( xHandler );

    // parser the stream
    try
    {
        xParser->parseStream( aParserInput );
    }
    catch( uno::Exception& )
    {
        mbError = true;
    }

    // release the signature reader
    mpXSecController->releaseSignatureReader( );

    return !mbError;
}

SignatureInformation XMLSignatureHelper::GetSignatureInformation( sal_Int32 nSecurityId ) const
{
    return mpXSecController->getSignatureInformation( nSecurityId );
}

SignatureInformations XMLSignatureHelper::GetSignatureInformations() const
{
    return mpXSecController->getSignatureInformations();
}

void XMLSignatureHelper::StartVerifySignatureElement()
{
    if ( !maStartVerifySignatureHdl.IsSet() || maStartVerifySignatureHdl.Call(nullptr) )
    {
        sal_Int32 nSignatureId = mpXSecController->getNewSecurityId();
        mpXSecController->addSignature( nSignatureId );
    }
}

namespace
{
bool lcl_isSignatureType(const beans::StringPair& rPair)
{
    return rPair.First == "Type" && rPair.Second == OOXML_SIGNATURE_SIGNATURE;
}
bool lcl_isSignatureOriginType(const beans::StringPair& rPair)
{
    return rPair.First == "Type" && rPair.Second == OOXML_SIGNATURE_ORIGIN;
}
}

bool XMLSignatureHelper::ReadAndVerifySignatureStorage(const uno::Reference<embed::XStorage>& xStorage, bool bCacheLastSignature)
{
    sal_Int32 nOpenMode = embed::ElementModes::READ;
    uno::Reference<container::XNameAccess> xNameAccess(xStorage, uno::UNO_QUERY);
    if (xNameAccess.is() && !xNameAccess->hasByName("_rels"))
    {
        SAL_WARN("xmlsecurity.helper", "expected stream, in signature storage but not found: _rels");
        return false;
    }

    uno::Reference<embed::XStorage> xSubStorage = xStorage->openStorageElement("_rels", nOpenMode);
    uno::Reference<io::XInputStream> xRelStream(xSubStorage->openStreamElement("origin.sigs.rels", nOpenMode), uno::UNO_QUERY);
    uno::Sequence< uno::Sequence<beans::StringPair> > aRelationsInfo;
    aRelationsInfo = comphelper::OFOPXMLHelper::ReadRelationsInfoSequence(xRelStream, "origin.sigs.rels", mxCtx);

    for (sal_Int32 i = 0; i < aRelationsInfo.getLength(); ++i)
    {
        const uno::Sequence<beans::StringPair>& rRelation = aRelationsInfo[i];
        auto aRelation = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(rRelation);
        if (std::any_of(aRelation.begin(), aRelation.end(), lcl_isSignatureType))
        {
            std::vector<beans::StringPair>::iterator it = std::find_if(aRelation.begin(), aRelation.end(), [](const beans::StringPair& rPair) { return rPair.First == "Target"; });
            if (it != aRelation.end())
            {
                if (xNameAccess.is() && !xNameAccess->hasByName(it->Second))
                {
                    SAL_WARN("xmlsecurity.helper", "expected stream, but not found: " << it->Second);
                    continue;
                }

                uno::Reference<io::XInputStream> xInputStream(xStorage->openStreamElement(it->Second, nOpenMode), uno::UNO_QUERY);
                if (!ReadAndVerifySignatureStorageStream(xInputStream))
                    return false;

                // By default, we cache. If it's requested, then we don't cache the last signature.
                bool bCache = true;
                if (!bCacheLastSignature && i == aRelationsInfo.getLength() - 1)
                    bCache = false;

                if (!bCache)
                    continue;
                // Store the contents of the stream as is, in case we need to write it back later.
                xInputStream.clear();
                xInputStream.set(xStorage->openStreamElement(it->Second, nOpenMode), uno::UNO_QUERY);
                uno::Reference<beans::XPropertySet> xPropertySet(xInputStream, uno::UNO_QUERY);
                if (!xPropertySet.is())
                    continue;

                sal_Int64 nSize = 0;
                xPropertySet->getPropertyValue("Size") >>= nSize;
                if (nSize < 0 || nSize > SAL_MAX_INT32)
                {
                    SAL_WARN("xmlsecurity.helper", "bogus signature size: " << nSize);
                    continue;
                }
                uno::Sequence<sal_Int8> aData;
                xInputStream->readBytes(aData, nSize);
                mpXSecController->setSignatureBytes(aData);
            }
        }
    }

    return true;
}

bool XMLSignatureHelper::ReadAndVerifySignatureStorageStream(const css::uno::Reference<css::io::XInputStream>& xInputStream)
{
    mbError = false;

    // Create the input source.
    xml::sax::InputSource aParserInput;
    aParserInput.aInputStream = xInputStream;

    // Create the sax parser.
    uno::Reference<xml::sax::XParser> xParser = xml::sax::Parser::create(mxCtx);

    // Create the signature reader.
    uno::Reference<xml::sax::XDocumentHandler> xHandler = mpXSecController->createSignatureReader(*this, embed::StorageFormats::OFOPXML);

    // Parser -> signature reader.
    xParser->setDocumentHandler(xHandler);

    // Parse the stream.
    try
    {
        xParser->parseStream(aParserInput);
    }
    catch(const uno::Exception&)
    {
        DBG_UNHANDLED_EXCEPTION("xmlsecurity.helper");
    }

    mpXSecController->releaseSignatureReader();

    return !mbError;
}

void XMLSignatureHelper::EnsureSignaturesRelation(const css::uno::Reference<css::embed::XStorage>& xStorage, bool bAdd)
{
    sal_Int32 nOpenMode = embed::ElementModes::READWRITE;
    uno::Reference<embed::XStorage> xSubStorage = xStorage->openStorageElement("_rels", nOpenMode);
    uno::Reference<io::XInputStream> xRelStream(xSubStorage->openStreamElement(".rels", nOpenMode), uno::UNO_QUERY);
    std::vector< uno::Sequence<beans::StringPair> > aRelationsInfo;
    aRelationsInfo = comphelper::sequenceToContainer< std::vector< uno::Sequence<beans::StringPair> > >(comphelper::OFOPXMLHelper::ReadRelationsInfoSequence(xRelStream, ".rels", mxCtx));

    // Do we have a relation already?
    bool bHaveRelation = false;
    int nCount = 0;
    for (const uno::Sequence<beans::StringPair>& rRelation : aRelationsInfo)
    {
        auto aRelation = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(rRelation);
        if (std::any_of(aRelation.begin(), aRelation.end(), lcl_isSignatureOriginType))
        {
            bHaveRelation = true;
            break;
        }
        ++nCount;
    }

    if (!bHaveRelation && bAdd)
    {
        // No, and have to add one.
        std::vector<beans::StringPair> aRelation;
        aRelation.emplace_back("Id", "rId" + OUString::number(++nCount));
        aRelation.emplace_back("Type", OOXML_SIGNATURE_ORIGIN);
        aRelation.emplace_back("Target", "_xmlsignatures/origin.sigs");
        aRelationsInfo.push_back(comphelper::containerToSequence(aRelation));
    }
    else if (bHaveRelation && !bAdd)
    {
        // Yes, and need to remove it.
        for (std::vector< uno::Sequence<beans::StringPair> >::iterator it = aRelationsInfo.begin(); it != aRelationsInfo.end();)
        {
            auto aRelation = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(*it);
            if (std::any_of(aRelation.begin(), aRelation.end(), lcl_isSignatureOriginType))
                it = aRelationsInfo.erase(it);
            else
                ++it;
        }
    }

    // Write it back.
    uno::Reference<io::XTruncate> xTruncate(xRelStream, uno::UNO_QUERY);
    xTruncate->truncate();
    uno::Reference<io::XOutputStream> xOutputStream(xRelStream, uno::UNO_QUERY);
    comphelper::OFOPXMLHelper::WriteRelationsInfoSequence(xOutputStream, comphelper::containerToSequence(aRelationsInfo), mxCtx);

    // Commit it.
    uno::Reference<embed::XTransactedObject> xTransact(xSubStorage, uno::UNO_QUERY);
    xTransact->commit();
    xTransact.set(xStorage, uno::UNO_QUERY);
    xTransact->commit();
}

void XMLSignatureHelper::ExportSignatureRelations(const css::uno::Reference<css::embed::XStorage>& xStorage, int nSignatureCount)
{
    // Write the empty file, its relations will be the signatures.
    sal_Int32 nOpenMode = embed::ElementModes::READWRITE;
    uno::Reference<io::XOutputStream> xOriginStream(xStorage->openStreamElement("origin.sigs", nOpenMode), uno::UNO_QUERY);
    uno::Reference<io::XTruncate> xTruncate(xOriginStream, uno::UNO_QUERY);
    xTruncate->truncate();
    xOriginStream->closeOutput();

    // Write the relations.
    uno::Reference<embed::XStorage> xSubStorage(xStorage->openStorageElement("_rels", nOpenMode), uno::UNO_QUERY);
    uno::Reference<io::XOutputStream> xRelStream(xSubStorage->openStreamElement("origin.sigs.rels", nOpenMode), uno::UNO_QUERY);
    std::vector< uno::Sequence<beans::StringPair> > aRelations;
    for (int i = 0; i < nSignatureCount; ++i)
    {
        std::vector<beans::StringPair> aRelation;
        aRelation.emplace_back("Id", "rId" + OUString::number(i + 1));
        aRelation.emplace_back("Type", OOXML_SIGNATURE_SIGNATURE);
        aRelation.emplace_back("Target", "sig" + OUString::number(i + 1) + ".xml");
        aRelations.push_back(comphelper::containerToSequence(aRelation));
    }
    comphelper::OFOPXMLHelper::WriteRelationsInfoSequence(xRelStream, comphelper::containerToSequence(aRelations), mxCtx);
    uno::Reference<embed::XTransactedObject> xTransact(xSubStorage, uno::UNO_QUERY);
    xTransact->commit();
}

void XMLSignatureHelper::ExportSignatureContentTypes(const css::uno::Reference<css::embed::XStorage>& xStorage, int nSignatureCount)
{
    uno::Reference<io::XStream> xStream(xStorage->openStreamElement("[Content_Types].xml", embed::ElementModes::READWRITE), uno::UNO_QUERY);
    uno::Reference<io::XInputStream> xInputStream = xStream->getInputStream();
    uno::Sequence< uno::Sequence<beans::StringPair> > aContentTypeInfo = comphelper::OFOPXMLHelper::ReadContentTypeSequence(xInputStream, mxCtx);
    if (aContentTypeInfo.getLength() < 2)
    {
        SAL_WARN("xmlsecurity.helper", "no defaults or overrides in aContentTypeInfo");
        return;
    }

    // Append rels and sigs to defaults, if it's not there already.
    uno::Sequence<beans::StringPair>& rDefaults = aContentTypeInfo[0];
    auto aDefaults = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(rDefaults);
    if (std::none_of(rDefaults.begin(), rDefaults.end(), [](const beans::StringPair& rPair) { return rPair.First == "rels"; }))
        aDefaults.emplace_back("rels", "application/vnd.openxmlformats-package.relationships+xml");

    if (std::none_of(rDefaults.begin(), rDefaults.end(), [](const beans::StringPair& rPair) { return rPair.First == "sigs"; }))
        aDefaults.emplace_back("sigs", "application/vnd.openxmlformats-package.digital-signature-origin");
    rDefaults = comphelper::containerToSequence(aDefaults);

    // Remove existing signature overrides.
    uno::Sequence<beans::StringPair>& rOverrides = aContentTypeInfo[1];
    auto aOverrides = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(rOverrides);
    aOverrides.erase(std::remove_if(aOverrides.begin(), aOverrides.end(), [](const beans::StringPair& rPair)
    {
        return rPair.First.startsWith("/_xmlsignatures/sig");
    }), aOverrides.end());

    // Add our signature overrides.
    for (int i = 1; i <= nSignatureCount; ++i)
        aOverrides.emplace_back("/_xmlsignatures/sig" + OUString::number(i) + ".xml", "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml");

    rOverrides = comphelper::containerToSequence(aOverrides);
    uno::Reference<io::XOutputStream> xOutputStream = xStream->getOutputStream();
    uno::Reference <io::XTruncate> xTruncate(xOutputStream, uno::UNO_QUERY);
    xTruncate->truncate();
    comphelper::OFOPXMLHelper::WriteContentSequence(xOutputStream, rDefaults, rOverrides, mxCtx);
    uno::Reference<embed::XTransactedObject> xTransact(xStorage, uno::UNO_QUERY);
    xTransact->commit();
}
void XMLSignatureHelper::CreateAndWriteOOXMLSignature(const uno::Reference<embed::XStorage>& xRootStorage, const uno::Reference<embed::XStorage>& xSignatureStorage, int nSignatureIndex)
{
    uno::Reference<io::XOutputStream> xOutputStream(xSignatureStorage->openStreamElement("sig" + OUString::number(nSignatureIndex) + ".xml", embed::ElementModes::READWRITE), uno::UNO_QUERY);
    uno::Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(mxCtx);
    xSaxWriter->setOutputStream(xOutputStream);
    xSaxWriter->startDocument();

    mbError = false;
    uno::Reference<xml::sax::XDocumentHandler> xDocumentHandler(xSaxWriter, uno::UNO_QUERY);
    if (!mpXSecController->WriteOOXMLSignature(xRootStorage, xDocumentHandler))
        mbError = true;

    xSaxWriter->endDocument();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
