/**
 * 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
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * XSEC
 *
 * DSIGTransformC14n := Class that performs C14n canonicalisation
 *
 * $Id: DSIGTransformC14n.cpp 1833341 2018-06-11 16:25:41Z scantor $
 *
 */

#include <xsec/dsig/DSIGSignature.hpp>
#include <xsec/dsig/DSIGTransformC14n.hpp>
#include <xsec/framework/XSECEnv.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/framework/XSECException.hpp>
#include <xsec/transformers/TXFMC14n.hpp>
#include <xsec/transformers/TXFMChain.hpp>

#include "../utils/XSECAlgorithmSupport.hpp"
#include "../utils/XSECDOMUtils.hpp"

XERCES_CPP_NAMESPACE_USE

// --------------------------------------------------------------------------------
//           Constructors and Destructors
// --------------------------------------------------------------------------------

DSIGTransformC14n::DSIGTransformC14n(const XSECEnv* env, DOMNode* node) :
DSIGTransform(env, node) {

    m_cMethod = NULL;
    mp_inclNSNode = NULL;
    mp_inclNSStr = NULL;
    m_exclusive = false;
    m_comments = false;
    m_onedotone = false;
}


DSIGTransformC14n::DSIGTransformC14n(const XSECEnv* env) :
DSIGTransform(env) {

    m_cMethod = NULL;
    mp_inclNSNode = NULL;
    mp_inclNSStr = NULL;
    m_exclusive = false;
    m_comments = false;
    m_onedotone = false;
}

DSIGTransformC14n::~DSIGTransformC14n() {};

// --------------------------------------------------------------------------------
//           Interface Methods
// --------------------------------------------------------------------------------

void DSIGTransformC14n::appendTransformer(TXFMChain* input) {

    TXFMC14n* c;

    XSECnew(c, TXFMC14n(mp_txfmNode->getOwnerDocument()));
    input->appendTxfm(c);

    if (m_comments)
        c->activateComments();
    else
        c->stripComments();

    // Check for exclusive and 1.1
    if (m_exclusive) {
        if (mp_inclNSStr == NULL) {
            c->setExclusive();
        }
        else {
            safeBuffer incl;
            incl << (*(mp_env->getSBFormatter()) << mp_inclNSStr);
            c->setExclusive(incl);
        }
    }
    else if (m_onedotone) {
        c->setInclusive11();
    }
}

DOMElement* DSIGTransformC14n::createBlankTransform(DOMDocument* parentDoc) {

    safeBuffer str;
    const XMLCh * prefix;
    DOMElement *ret;
    DOMDocument *doc = mp_env->getParentDocument();

    prefix = mp_env->getDSIGNSPrefix();

    // Create the transform node
    makeQName(str, prefix, "Transform");
    ret = doc->createElementNS(DSIGConstants::s_unicodeStrURIDSIG, str.rawXMLChBuffer());
    ret->setAttributeNS(NULL,DSIGConstants::s_unicodeStrAlgorithm, DSIGConstants::s_unicodeStrURIC14N_NOC);

    m_cMethod = ret->getAttributeNS(NULL, DSIGConstants::s_unicodeStrAlgorithm);
    m_comments = false;
    m_exclusive = false;
    m_onedotone = false;

    mp_txfmNode = ret;
    mp_inclNSStr = NULL;
    mp_inclNSNode = NULL;

    return ret;
}

void DSIGTransformC14n::load() {

    DOMNamedNodeMap* atts;
    DOMNode* att;

    // Read the URI for the type
    if (mp_txfmNode == NULL) {
        throw XSECException(XSECException::ExpectedDSIGChildNotFound,
            "Expected <Transform> Node in DSIGTrasnformC14n::load");
    }

    atts = mp_txfmNode->getAttributes();

    if (atts == NULL ||
        ((att = atts->getNamedItem(DSIGConstants::s_unicodeStrAlgorithm)) == NULL)) {

        throw XSECException(XSECException::ExpectedDSIGChildNotFound,
            "Expected to find Algorithm attribute in <Transform> node");
    }

    m_cMethod = att->getNodeValue();

    if (!XSECAlgorithmSupport::evalCanonicalizationMethod(m_cMethod, m_exclusive, m_comments, m_onedotone)) {
        throw XSECException(XSECException::TransformError,
            "Unexpected URI found in canonicalization <Transform>");
    }

    // Determine whether there is an InclusiveNamespaces list

    if (m_exclusive) {

        // Exclusive, so there may be an InclusiveNamespaces node

        DOMNode* inclNSNode = mp_txfmNode->getFirstChild();

        while (inclNSNode != NULL && (inclNSNode->getNodeType() != DOMNode::ELEMENT_NODE ||
            !strEquals(getECLocalName(inclNSNode), "InclusiveNamespaces")))
                inclNSNode = inclNSNode->getNextSibling();

        if (inclNSNode != 0) {

            mp_inclNSNode = static_cast<DOMElement *>(inclNSNode);

            // Have a prefix list
            atts = mp_inclNSNode->getAttributes();
            safeBuffer inSB;

            if (atts == 0 || ((att = atts->getNamedItem(MAKE_UNICODE_STRING("PrefixList"))) == NULL)) {
                throw XSECException(XSECException::ExpectedDSIGChildNotFound,
                    "Expected PrefixList in InclusiveNamespaces");
            }

            mp_inclNSStr = att->getNodeValue();
        }
    }
}

// --------------------------------------------------------------------------------
//           Canonicalization Specific Methods
// --------------------------------------------------------------------------------


void DSIGTransformC14n::setCanonicalizationMethod(const XMLCh* uri) {

    bool exclusive;
    bool comments;
    bool onedotone;

    if (mp_txfmNode == NULL || !XSECAlgorithmSupport::evalCanonicalizationMethod(uri, exclusive, comments, onedotone)) {
        throw XSECException(XSECException::TransformError,
            "Either method unknown or Node not set in setCanonicalizationMethod");
    }

    // Switching from exclusive to inclusive?
    if (!exclusive && m_exclusive) {
        if (mp_inclNSNode != 0) {

            mp_txfmNode->removeChild(mp_inclNSNode);
            mp_inclNSNode->release();        // No longer required

            mp_inclNSNode = NULL;
            mp_inclNSStr = NULL;
        }
    }

    // Now do the set.
    ((DOMElement *) mp_txfmNode)->setAttributeNS(NULL, DSIGConstants::s_unicodeStrAlgorithm, uri);
    m_cMethod = ((DOMElement *) mp_txfmNode)->getAttributeNS(NULL, DSIGConstants::s_unicodeStrAlgorithm);

    m_exclusive = exclusive;
    m_comments = comments;
    m_onedotone = onedotone;
}

const XMLCh* DSIGTransformC14n::getCanonicalizationMethod() const {
    return m_cMethod;
}

void DSIGTransformC14n::createInclusiveNamespaceNode() {

    // Creates an empty inclusiveNamespace node.  Does _not_ set the prefixlist attribute

    if (mp_inclNSNode != NULL)
        return;        // Already exists

    safeBuffer str;
    const XMLCh * prefix;
    DOMDocument *doc = mp_env->getParentDocument();

    // Use the Exclusive Canonicalisation prefix
    prefix = mp_env->getECNSPrefix();

    // Create the transform node
    makeQName(str, prefix, "InclusiveNamespaces");
    mp_inclNSNode = doc->createElementNS(DSIGConstants::s_unicodeStrURIEC, str.rawXMLChBuffer());

    // Add the node to the owner element
    mp_env->doPrettyPrint(mp_txfmNode);
    mp_txfmNode->appendChild(mp_inclNSNode);
    mp_env->doPrettyPrint(mp_txfmNode);

    // Set the namespace attribute
    if (prefix[0] == '\0') {
        str.sbTranscodeIn("xmlns");
    }
    else {
        str.sbTranscodeIn("xmlns:");
        str.sbXMLChCat(prefix);
    }

    mp_inclNSNode->setAttributeNS(DSIGConstants::s_unicodeStrURIXMLNS,
                            str.rawXMLChBuffer(),
                            DSIGConstants::s_unicodeStrURIEC);
}

void DSIGTransformC14n::setInclusiveNamespaces(const XMLCh* ns) {

    // Set all the namespaces at once

    if (!m_exclusive) {
        throw XSECException(XSECException::TransformError,
            "Cannot set inclusive namespaces on non Exclusive Canonicalization");
    }

    if (mp_inclNSNode == NULL) {
        // Create the transform node
        createInclusiveNamespaceNode();
    }

    // Now create the prefix list

    mp_inclNSNode->setAttributeNS(NULL,MAKE_UNICODE_STRING("PrefixList"), ns);
    mp_inclNSStr = mp_inclNSNode->getAttributes()->getNamedItem(MAKE_UNICODE_STRING("PrefixList"))->getNodeValue();
}


void DSIGTransformC14n::addInclusiveNamespace(const char* ns) {

    if (!m_exclusive) {
        throw XSECException(XSECException::TransformError,
            "Cannot set inclusive namespaces on non Exclusive Canonicalization");
    }

    if (mp_inclNSNode == NULL) {
        // Create the transform node
        createInclusiveNamespaceNode();

        // Now create the prefix list

        mp_inclNSNode->setAttributeNS(NULL,MAKE_UNICODE_STRING("PrefixList"), MAKE_UNICODE_STRING(ns));
        mp_inclNSStr = mp_inclNSNode->getAttributes()->getNamedItem(MAKE_UNICODE_STRING("PrefixList"))->getNodeValue();
    }
    else {
        // More tricky
        safeBuffer str;

        str << (*(mp_env->getSBFormatter()) << mp_inclNSStr);
        str.sbStrcatIn(" ");
        str.sbStrcatIn((char *) ns);
        mp_inclNSNode->setAttributeNS(NULL,MAKE_UNICODE_STRING("PrefixList"), str.sbStrToXMLCh());
        mp_inclNSStr = mp_inclNSNode->getAttributes()->getNamedItem(MAKE_UNICODE_STRING("PrefixList"))->getNodeValue();
    }
}

const XMLCh* DSIGTransformC14n::getPrefixList() const {
    return mp_inclNSStr;
}

void DSIGTransformC14n::clearInclusiveNamespaces() {

    if (mp_inclNSNode != 0) {
        mp_txfmNode->removeChild(mp_inclNSNode);
        mp_inclNSNode->release();        // No longer required

        mp_inclNSNode = NULL;
        mp_inclNSStr = NULL;
    }
}
