/**
 * 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
 *
 * DSIGSignedInfo := Class for checking and setting up signed Info nodes in a DSIG signature
 *
 * $Id: DSIGSignedInfo.cpp 1833341 2018-06-11 16:25:41Z scantor $
 *
 */

// XSEC Includes
#include <xsec/dsig/DSIGReference.hpp>
#include <xsec/dsig/DSIGSignedInfo.hpp>
#include <xsec/dsig/DSIGSignature.hpp>
#include <xsec/framework/XSECEnv.hpp>
#include <xsec/framework/XSECError.hpp>

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

#include <xercesc/util/Janitor.hpp>

XERCES_CPP_NAMESPACE_USE

// Constructors and Destructors

DSIGSignedInfo::DSIGSignedInfo(DOMDocument* doc,
		XSECSafeBufferFormatter* pFormatter,
		DOMNode* signedInfoNode, const XSECEnv* env) {

	mp_doc = doc;
	m_HMACOutputLength = 0;
	mp_formatter = pFormatter;
	mp_signedInfoNode = signedInfoNode;
	mp_canonicalizationMethod = NULL;
	mp_algorithmURI = NULL;
	mp_env = env;
	mp_referenceList = NULL;
	m_loaded = false;
}

DSIGSignedInfo::DSIGSignedInfo(DOMDocument* doc,
		XSECSafeBufferFormatter* pFormatter,
		const XSECEnv* env) {

	mp_doc = doc;
	m_HMACOutputLength = 0;
	mp_formatter = pFormatter;
	mp_signedInfoNode = NULL;
	mp_canonicalizationMethod = NULL;
	mp_algorithmURI = NULL;
	mp_env = env;
	mp_referenceList = NULL;
	m_loaded = false;
}

DSIGSignedInfo::~DSIGSignedInfo() {

	mp_formatter = NULL;
	mp_env = NULL;
	if (mp_referenceList != NULL) {
		delete mp_referenceList;
		mp_referenceList = NULL;
	}
}


// --------------------------------------------------------------------------------
//           Verify each reference element
// --------------------------------------------------------------------------------

bool DSIGSignedInfo::verify(safeBuffer& errStr) const {
	return DSIGReference::verifyReferenceList(mp_referenceList, errStr);
}

// --------------------------------------------------------------------------------
//           Calculate and set hash values for each reference element
// --------------------------------------------------------------------------------

void DSIGSignedInfo::hash(bool interlockingReferences) const {
	DSIGReference::hashReferenceList(mp_referenceList, interlockingReferences);
}

// --------------------------------------------------------------------------------
//           Create an empty reference in the signed info
// --------------------------------------------------------------------------------


DSIGReference* DSIGSignedInfo::createReference(
		const XMLCh* URI,
		const XMLCh* hashAlgorithmURI,
		const XMLCh* type) {

	DSIGReference* ref;
	XSECnew(ref, DSIGReference(mp_env));
	Janitor<DSIGReference> j_ref(ref);

	DOMNode* refNode = ref->createBlankReference(URI, hashAlgorithmURI, type);

	// Add the node to the end of the childeren
	mp_signedInfoNode->appendChild(refNode);
	mp_env->doPrettyPrint(mp_signedInfoNode);

	// Add to the reference List
	j_ref.release();
	mp_referenceList->addReference(ref);

	return ref;
}

DSIGReference* DSIGSignedInfo::removeReference(DSIGReferenceList::size_type index) {

    DSIGReference* ret = mp_referenceList ? mp_referenceList->removeReference(index): NULL;
    if (ret && mp_signedInfoNode) {
        mp_signedInfoNode->removeChild(ret->mp_referenceNode);
        mp_env->doPrettyPrint(mp_signedInfoNode);
    }

    return ret;
}


// --------------------------------------------------------------------------------
//           Create an empty SignedInfo
// --------------------------------------------------------------------------------

DOMElement* DSIGSignedInfo::createBlankSignedInfo(
			const XMLCh* canonicalizationAlgorithmURI,
			const XMLCh* signatureAlgorithmURI) {

	safeBuffer str;
	const XMLCh* prefixNS = mp_env->getDSIGNSPrefix();

	makeQName(str, prefixNS, "SignedInfo");

	DOMElement* ret = mp_doc->createElementNS(DSIGConstants::s_unicodeStrURIDSIG,
								str.rawXMLChBuffer());
	
	mp_signedInfoNode = ret;

	// Canonicalization

	DOMElement *canMeth = mp_doc->createElementNS(DSIGConstants::s_unicodeStrURIDSIG, 
			makeQName(str, prefixNS, "CanonicalizationMethod").rawXMLChBuffer());

	mp_env->doPrettyPrint(mp_signedInfoNode);
	mp_signedInfoNode->appendChild(canMeth);
	mp_env->doPrettyPrint(mp_signedInfoNode);

	canMeth->setAttributeNS(NULL,DSIGConstants::s_unicodeStrAlgorithm,
		canonicalizationAlgorithmURI);

	// Store the method URI internally.
	mp_canonicalizationMethod = canMeth->getAttributeNS(NULL, DSIGConstants::s_unicodeStrAlgorithm);

	// Now the SignatureMethod

	DOMElement *sigMeth = mp_doc->createElementNS(DSIGConstants::s_unicodeStrURIDSIG, 
			makeQName(str, prefixNS, "SignatureMethod").rawXMLChBuffer());

	mp_signedInfoNode->appendChild(sigMeth);
	mp_env->doPrettyPrint(mp_signedInfoNode);

	sigMeth->setAttributeNS(NULL, DSIGConstants::s_unicodeStrAlgorithm, 
		signatureAlgorithmURI);

	// Store the algorithm URI internally
	mp_algorithmURI = sigMeth->getAttributeNS(NULL, DSIGConstants::s_unicodeStrAlgorithm);

	// Create an empty reference list

	XSECnew(mp_referenceList, DSIGReferenceList());

	return ret;

}


// --------------------------------------------------------------------------------
//           Load the SignedInfo
// --------------------------------------------------------------------------------

void DSIGSignedInfo::load() {

	if (mp_signedInfoNode == 0) {
		// Attempt to load an empty signature element
		throw XSECException(XSECException::LoadEmptySignedInfo);
	}

	if (!strEquals(getDSIGLocalName(mp_signedInfoNode), "SignedInfo")) {
		throw XSECException(XSECException::LoadNonSignedInfo);
	}

	DOMNode* tmpSI = mp_signedInfoNode->getFirstChild();

	// Check for CanonicalizationMethod

	while (tmpSI != 0 && (tmpSI->getNodeType() != DOMNode::ELEMENT_NODE)) {
		if (tmpSI->getNodeType() == DOMNode::ENTITY_REFERENCE_NODE) {
			throw XSECException(XSECException::ExpectedDSIGChildNotFound,
				"EntityReference nodes in <SignedInfo> are unsupported.");
		}
		// Skip text and comments
		tmpSI = tmpSI->getNextSibling();
	}

	if (tmpSI == 0 || !strEquals(getDSIGLocalName(tmpSI), "CanonicalizationMethod")) {
		throw XSECException(XSECException::ExpectedDSIGChildNotFound, 
				"Expected <CanonicalizationMethod> as first child of <SignedInfo>");
	}

	// Determine what the canonicalization method is
	DOMNamedNodeMap* tmpAtts = tmpSI->getAttributes();

	DOMNode* algorithm = tmpAtts->getNamedItem(DSIGConstants::s_unicodeStrAlgorithm);

	if (algorithm == 0) {
		throw XSECException(XSECException::ExpectedDSIGChildNotFound,
					"Expected Algorithm attribute in <CanonicalizationMethod>");
	}

	mp_canonicalizationMethod = algorithm->getNodeValue();


	// Now load the SignatureMethod

	tmpSI = tmpSI->getNextSibling();

	while (tmpSI != 0 && (tmpSI->getNodeType() != DOMNode::ELEMENT_NODE)) {
		if (tmpSI->getNodeType() == DOMNode::ENTITY_REFERENCE_NODE) {
			throw XSECException(XSECException::ExpectedDSIGChildNotFound,
				"EntityReference nodes in <SignedInfo> are unsupported.");
		}
		// Skip text and comments
		tmpSI = tmpSI->getNextSibling();
	}

	if (tmpSI == 0 || !strEquals(getDSIGLocalName(tmpSI), "SignatureMethod")) {
		throw XSECException(XSECException::ExpectedDSIGChildNotFound, 
				"Expected <SignatureMethod> as child of <SignedInfo>");
	}


	// Determine the algorithms used to sign this document

	tmpAtts = tmpSI->getAttributes();

	algorithm = tmpAtts->getNamedItem(DSIGConstants::s_unicodeStrAlgorithm);
	
	if (algorithm == 0) {
		throw XSECException(XSECException::ExpectedDSIGChildNotFound,
					"Expected Algorithm attribute in <SignatureMethod>");
	}
	
	mp_algorithmURI = algorithm->getNodeValue();

	/* NOTE - as of version 1.3.1 all code relating to parsing the algorithm 
	 * has been removed.  This should all be handled inside the algorithm mappers.
	 * Having code here restricts available algorithms, as this code is not extended for
	 * new algorithms.
	 */

	/* Look for maximum output value. Really only applies to HMACs, but as we no
	 * longer know at this point if this is an HMAC, we need to check. */

	DOMNode *tmpSOV = tmpSI->getFirstChild();
	while (tmpSOV != NULL &&
		(tmpSOV->getNodeType() != DOMNode::ELEMENT_NODE || !strEquals(getDSIGLocalName(tmpSOV), "HMACOutputLength"))) {
		if (tmpSOV->getNodeType() == DOMNode::ENTITY_REFERENCE_NODE) {
			throw XSECException(XSECException::ExpectedDSIGChildNotFound,
				"EntityReference nodes in <SignedInfo> are unsupported.");
		}
		tmpSOV = tmpSOV->getNextSibling();
	}

	if (tmpSOV != NULL) {

		// Have a max output value!
		tmpSOV = tmpSOV->getFirstChild();
		while (tmpSOV != NULL && tmpSOV->getNodeType() != DOMNode::TEXT_NODE)
			tmpSOV = tmpSOV->getNextSibling();

		if (tmpSOV != NULL) {

			safeBuffer val;
			val << (*mp_formatter << tmpSOV->getNodeValue());
			m_HMACOutputLength = atoi((char *) val.rawBuffer());

		}
	}

	// Now look at references....

	tmpSI = tmpSI->getNextSibling();

	// Run through the rest of the elements until done

	while (tmpSI != 0 && (tmpSI->getNodeType() != DOMNode::ELEMENT_NODE)) {
		if (tmpSI->getNodeType() == DOMNode::ENTITY_REFERENCE_NODE) {
			throw XSECException(XSECException::ExpectedDSIGChildNotFound,
				"EntityReference nodes in <SignedInfo> are unsupported.");
		}
		// Skip text and comments
		tmpSI = tmpSI->getNextSibling();
	}

	if (tmpSI != NULL) {
		// Have an element node - should be a reference, so let's load the list
		mp_referenceList = DSIGReference::loadReferenceListFromXML(mp_env, tmpSI);
	}

}
