/**
 * 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
 *
 * DSIGKeyInfoList := Class for Loading and storing a list of KeyInfo elements
 *
 * Author(s): Berin Lautenbach
 *
 * $Id: DSIGKeyInfoList.cpp 1833341 2018-06-11 16:25:41Z scantor $
 *
 */

// XSEC Includes
#include <xsec/dsig/DSIGKeyInfoList.hpp>
#include <xsec/dsig/DSIGKeyInfoX509.hpp>
#include <xsec/dsig/DSIGKeyInfoName.hpp>
#include <xsec/dsig/DSIGKeyInfoValue.hpp>
#include <xsec/dsig/DSIGKeyInfoDEREncoded.hpp>
#include <xsec/dsig/DSIGKeyInfoPGPData.hpp>
#include <xsec/dsig/DSIGKeyInfoSPKIData.hpp>
#include <xsec/dsig/DSIGKeyInfoMgmtData.hpp>
#include <xsec/dsig/DSIGKeyInfoExt.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/dsig/DSIGSignature.hpp>
#include <xsec/dsig/DSIGReference.hpp>
#include <xsec/dsig/DSIGTransformList.hpp>
#include <xsec/framework/XSECEnv.hpp>
#include <xsec/transformers/TXFMChain.hpp>
#include <xsec/transformers/TXFMBase.hpp>

#include "../utils/XSECDOMUtils.hpp"
#include "../xenc/impl/XENCEncryptedKeyImpl.hpp"

#include <xercesc/util/Janitor.hpp>

XERCES_CPP_NAMESPACE_USE

DSIGKeyInfoList::DSIGKeyInfoList(const XSECEnv * env) :
mp_env(env),
mp_keyInfoNode(NULL) {}

DSIGKeyInfoList::~DSIGKeyInfoList() {

	empty();

}

// Actions

void DSIGKeyInfoList::addKeyInfo(DSIGKeyInfo * ref) {

	m_keyInfoList.push_back(ref);

}

DSIGKeyInfo * DSIGKeyInfoList::removeKeyInfo(size_type index) {

	if (index < m_keyInfoList.size())
		return m_keyInfoList[index];

	return NULL;

}

size_t DSIGKeyInfoList::getSize() const {

	return m_keyInfoList.size();

}


DSIGKeyInfo * DSIGKeyInfoList::item(size_type index) {

	if (index < m_keyInfoList.size())
		return m_keyInfoList[index];

	return NULL;

}

const DSIGKeyInfo * DSIGKeyInfoList::item(size_type index) const {

	if (index < m_keyInfoList.size())
		return m_keyInfoList[index];

	return NULL;

}

void DSIGKeyInfoList::empty() {

	size_type i, s;
	s = getSize();

	for (i = 0; i < s; ++i)
		delete m_keyInfoList[i];

	m_keyInfoList.clear();

}

bool DSIGKeyInfoList::isEmpty() const {

		return (m_keyInfoList.size() == 0);

}

// --------------------------------------------------------------------------------
//           Add a KeyInfo based on XML DomNode source
// --------------------------------------------------------------------------------


bool DSIGKeyInfoList::addXMLKeyInfo(DOMNode *ki) {

	// return true if successful - does not throw if the node type is unknown

	if (ki == 0)
		return false;

	DSIGKeyInfo * k;

	if (strEquals(getDSIGLocalName(ki), "X509Data")) {

		// Have a certificate!
		XSECnew(k, DSIGKeyInfoX509(mp_env, ki));
	}

	else if (strEquals(getDSIGLocalName(ki), "KeyName")) {

		XSECnew(k, DSIGKeyInfoName(mp_env, ki));
	}

	else if (strEquals(getDSIGLocalName(ki), "KeyValue")) {

		XSECnew(k, DSIGKeyInfoValue(mp_env, ki));
	}

	else if (strEquals(getDSIGLocalName(ki), "PGPData")) {

		XSECnew(k, DSIGKeyInfoPGPData(mp_env, ki));
	}

	else if (strEquals(getDSIGLocalName(ki), "SPKIData")) {

		XSECnew(k, DSIGKeyInfoSPKIData(mp_env, ki));

	}

	else if (strEquals(getDSIGLocalName(ki), "MgmtData")) {

		XSECnew(k, DSIGKeyInfoMgmtData(mp_env, ki));

	}
    
    else if (strEquals(getDSIG11LocalName(ki), "DEREncodedKeyValue")) {

		XSECnew(k, DSIGKeyInfoDEREncoded(mp_env, ki));

	}

	else if (strEquals(getXENCLocalName(ki), "EncryptedKey")) {

		XSECnew(k, XENCEncryptedKeyImpl(mp_env, (DOMElement *) ki));

	}

	else {

	    XSECnew(k, DSIGKeyInfoExt(mp_env, ki));

	}

	// Now we know what the element type is - do the load and save

	try {
		k->load();
	}
	catch (...) {
		delete k;
		throw;
	}

	// Add
	this->addKeyInfo(k);

	return true;

}

// --------------------------------------------------------------------------------
//           Retrieve a complete KeyInfo list
// --------------------------------------------------------------------------------


bool DSIGKeyInfoList::loadListFromXML(DOMNode * node) {

	if (node == NULL || !strEquals(getDSIGLocalName(node), "KeyInfo")) {
		throw XSECException(XSECException::ExpectedDSIGChildNotFound,
			"DSIGKeyInfoList::loadListFromXML - expected KeyInfo node");
	}

	DOMNode *tmpKI = findFirstChildOfType(node, DOMNode::ELEMENT_NODE);

	while (tmpKI != 0) {

		// Find out what kind of KeyInfo child it is

		if (tmpKI != 0 && strEquals(getDSIGLocalName(tmpKI), "RetrievalMethod")) {

			// A reference to key information held elsewhere

			const XMLCh * URI = NULL;
			TXFMBase * currentTxfm;
			bool isRawX509 = false;

			DOMNamedNodeMap *atts = tmpKI->getAttributes();
			const XMLCh * name;
			XMLSize_t size;

			if (atts == 0 || (size = atts->getLength()) == 0)
				return true;

			for (XMLSize_t i = 0; i < size; ++i) {

				name = atts->item(i)->getNodeName();

				if (strEquals(name, "URI")) {
					URI  = atts->item(i)->getNodeValue();
				}

				else if (strEquals(name, "Type")) {

					// Check if this is a raw X509 cert
					if (strEquals(atts->item(i)->getNodeValue(), DSIGConstants::s_unicodeStrURIRawX509)) {
						isRawX509 = true;
					}

				}

				else if (strEquals(name, "Id")) {

					// For now ignore

				}

				else {
					safeBuffer tmp, error;

					error << (*mp_env->getSBFormatter() << name);
					tmp.sbStrcpyIn("Unknown attribute in <RetrievalMethod> Element : ");
					tmp.sbStrcatIn(error);

					throw XSECException(XSECException::UnknownDSIGAttribute, tmp.rawCharBuffer());

				}

			}

			if (isRawX509 == true) {

				if (URI == NULL) {

					throw XSECException(XSECException::ExpectedDSIGChildNotFound,
						"Expected to find a URI attribute in a rawX509RetrievalMethod KeyInfo");

				}

				DSIGKeyInfoX509 * x509;
				XSECnew(x509, DSIGKeyInfoX509(mp_env));
				x509->setRawRetrievalURI(URI);

				addKeyInfo(x509);

			}

			else {

				// Find base transform using the base URI
				currentTxfm = DSIGReference::getURIBaseTXFM(mp_env->getParentDocument(), URI, mp_env);
				TXFMChain * chain;
				XSECnew(chain, TXFMChain(currentTxfm));
				Janitor<TXFMChain> j_chain(chain);

				// Now check for transforms
				DOMNode * tmpTran = tmpKI->getFirstChild();

				while (tmpTran != 0 && (tmpTran->getNodeType() != DOMNode::ELEMENT_NODE))
					// Skip text and comments
				    tmpTran = tmpTran->getNextSibling();

				if (tmpTran != 0 && strEquals(getDSIGLocalName(tmpTran), "Transforms")) {


					// Process the transforms using the static function.
					// For the moment we don't really support remote KeyInfos, so
					// Just built the transform list, process it and then destroy it.

					DSIGTransformList * l = DSIGReference::loadTransforms(
					    tmpTran,
						mp_env->getSBFormatter(),
						mp_env);

					DSIGTransformList::TransformListVectorType::size_type size, i;
					size = l->getSize();
					for (i = 0; i < size; ++ i) {
						try {
							l->item(i)->appendTransformer(chain);
						}
						catch (...) {
							delete l;
							throw;
						}
					}

					delete l;

				}

				// Find out the type of the final transform and process accordingly

				TXFMBase::nodeType type = chain->getLastTxfm()->getNodeType();

				XSECXPathNodeList lst;
				const DOMNode * element;

				switch (type) {

				case TXFMBase::DOM_NODE_DOCUMENT :

					break;

				case TXFMBase::DOM_NODE_DOCUMENT_FRAGMENT :

					element = chain->getLastTxfm()->getFragmentNode();
					if (element != NULL)
						addXMLKeyInfo((DOMNode *) element);

					break;

				case TXFMBase::DOM_NODE_XPATH_NODESET :

					lst = chain->getLastTxfm()->getXPathNodeList();
					element = lst.getFirstNode();

					while (element != NULL) {

						// Try to add each element - just call KeyInfoList add as it will
						// do the check to see if it is a valid KeyInfo

						addXMLKeyInfo((DOMNode *) element);
						element = lst.getNextNode();

					}

					break;

				default :

					throw XSECException(XSECException::XPathError);

				}

				// Delete the transform chain
				chain->getLastTxfm()->deleteExpandedNameSpaces();

				// Janitor will clean up chain
			}

		} /* if getNodeName == Retrieval Method */

		// Now just run through each node type in turn to process "local" KeyInfos

		else {
            // This used to check the return value, and throw if the child was unknown.
            // Now, should handle all cases.
            addXMLKeyInfo(tmpKI);
		}

		if (tmpKI != NULL)
			tmpKI = tmpKI->getNextSibling();

		while (tmpKI != NULL && (tmpKI->getNodeType() != DOMNode::ELEMENT_NODE))
			tmpKI = tmpKI->getNextSibling();

	}

	return true;
}

// --------------------------------------------------------------------------------
//           Create new KeyInfo elements
// --------------------------------------------------------------------------------

DOMElement * DSIGKeyInfoList::createKeyInfo(void) {

	// Assume that someone else has looked after the DOM
	empty();

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

	makeQName(str, mp_env->getDSIGNSPrefix(), "KeyInfo");

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

	mp_keyInfoNode = ret;
	mp_env->doPrettyPrint(mp_keyInfoNode);

	return ret;

}


DSIGKeyInfoValue * DSIGKeyInfoList::appendDSAKeyValue(const XMLCh * P,
						   const XMLCh * Q,
						   const XMLCh * G,
						   const XMLCh * Y) {

	if (mp_keyInfoNode == NULL) {

		throw XSECException(XSECException::KeyInfoError,
			"KeyInfoList - Attempt to create DSAKeyValue before creating KeyInfo");

	}

	// Create the new element
	DSIGKeyInfoValue * v;
	XSECnew(v, DSIGKeyInfoValue(mp_env));

	mp_keyInfoNode->appendChild(v->createBlankDSAKeyValue(P, Q, G, Y));
	mp_env->doPrettyPrint(mp_keyInfoNode);

	// Add to the list
	addKeyInfo(v);

	return v;

}

DSIGKeyInfoValue * DSIGKeyInfoList::appendRSAKeyValue(const XMLCh * modulus,
						   const XMLCh * exponent) {

	if (mp_keyInfoNode == NULL) {

		throw XSECException(XSECException::KeyInfoError,
			"KeyInfoList - Attempt to create RSAKeyValue before creating KeyInfo");

	}

	// Create the new element
	DSIGKeyInfoValue * v;
	XSECnew(v, DSIGKeyInfoValue(mp_env));

	mp_keyInfoNode->appendChild(v->createBlankRSAKeyValue(modulus, exponent));
	mp_env->doPrettyPrint(mp_keyInfoNode);

	// Add to the list
	addKeyInfo(v);

	return v;

}


DSIGKeyInfoX509 * DSIGKeyInfoList::appendX509Data(void) {

	if (mp_keyInfoNode == NULL) {

		throw XSECException(XSECException::KeyInfoError,
			"KeyInfoList - Attempt to create X509Data before creating KeyInfo");

	}

	DSIGKeyInfoX509 * x;

	XSECnew(x, DSIGKeyInfoX509(mp_env));

	mp_keyInfoNode->appendChild(x->createBlankX509Data());
	mp_env->doPrettyPrint(mp_keyInfoNode);

	// Add to the list
	addKeyInfo(x);

	return x;

}

DSIGKeyInfoName * DSIGKeyInfoList::appendKeyName(const XMLCh * name, bool isDName) {

	if (mp_keyInfoNode == NULL) {

		throw XSECException(XSECException::KeyInfoError,
			"KeyInfoList - Attempt to create KeyName before creating KeyInfo");

	}

	DSIGKeyInfoName * n;

	XSECnew(n, DSIGKeyInfoName(mp_env));

	mp_keyInfoNode->appendChild(n->createBlankKeyName(name, isDName));
	mp_env->doPrettyPrint(mp_keyInfoNode);

	// Add to the list
	addKeyInfo(n);

	return n;

}

DSIGKeyInfoPGPData * DSIGKeyInfoList::appendPGPData(const XMLCh * id, const XMLCh * packet) {

	if (mp_keyInfoNode == NULL) {

		throw XSECException(XSECException::KeyInfoError,
			"KeyInfoList - Attempt to create PGPData before creating KeyInfo");

	}

	DSIGKeyInfoPGPData * p;

	XSECnew(p, DSIGKeyInfoPGPData(mp_env));

	mp_keyInfoNode->appendChild(p->createBlankPGPData(id, packet));
	mp_env->doPrettyPrint(mp_keyInfoNode);

	addKeyInfo(p);

	return p;

}

DSIGKeyInfoSPKIData * DSIGKeyInfoList::appendSPKIData(const XMLCh * sexp) {

	if (mp_keyInfoNode == NULL) {

		throw XSECException(XSECException::KeyInfoError,
			"KeyInfoList - Attempt to create SPKIData before creating KeyInfo");

	}

	DSIGKeyInfoSPKIData * s;

	XSECnew(s, DSIGKeyInfoSPKIData(mp_env));

	mp_keyInfoNode->appendChild(s->createBlankSPKIData(sexp));
	mp_env->doPrettyPrint(mp_keyInfoNode);

	addKeyInfo(s);

	return s;

}

DSIGKeyInfoMgmtData * DSIGKeyInfoList::appendMgmtData(const XMLCh * data) {

	if (mp_keyInfoNode == NULL) {

		throw XSECException(XSECException::KeyInfoError,
			"KeyInfoList - Attempt to create MgmtData before creating KeyInfo");

	}

	DSIGKeyInfoMgmtData * m;

	XSECnew(m, DSIGKeyInfoMgmtData(mp_env));

	mp_keyInfoNode->appendChild(m->createBlankMgmtData(data));
	mp_env->doPrettyPrint(mp_keyInfoNode);

	addKeyInfo(m);

	return m;

}

DSIGKeyInfoDEREncoded * DSIGKeyInfoList::appendDEREncoded(const XMLCh * data) {

	if (mp_keyInfoNode == NULL) {

		throw XSECException(XSECException::KeyInfoError,
			"KeyInfoList - Attempt to create DEREncodedKeyValue before creating KeyInfo");

	}

	DSIGKeyInfoDEREncoded * d;

	XSECnew(d, DSIGKeyInfoDEREncoded(mp_env));

	mp_keyInfoNode->appendChild(d->createBlankDEREncoded(data));
	mp_env->doPrettyPrint(mp_keyInfoNode);

	addKeyInfo(d);

	return d;

}

// --------------------------------------------------------------------------------
//           Some helper functions
// --------------------------------------------------------------------------------

void DSIGKeyInfoList::addAndInsertKeyInfo(DSIGKeyInfo * ref) {

	if (mp_keyInfoNode == NULL) {

		throw XSECException(XSECException::KeyInfoError,
			"KeyInfoList - Attempt to create child before creating KeyInfo");

	}

	mp_keyInfoNode->appendChild(ref->getKeyInfoDOMNode());
	mp_env->doPrettyPrint(mp_keyInfoNode);

	addKeyInfo(ref);

}
