/**
 * 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
 *
 * XSECDOMUtils:= Utilities to manipulate DOM within XML-SECURITY
 *
 * $Id: XSECDOMUtils.cpp 1833341 2018-06-11 16:25:41Z scantor $
 *
 */

// XSEC

#include <xsec/framework/XSECError.hpp>
#include <xsec/xkms/XKMSConstants.hpp>

#include "XSECDOMUtils.hpp"

// Xerces

#include <xercesc/util/XMLUniDefs.hpp>
#include <xercesc/util/Janitor.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/TransService.hpp>

#include <string.h>

XERCES_CPP_NAMESPACE_USE

// --------------------------------------------------------------------------------
//           Utilities to manipulate DSIG namespaces
// --------------------------------------------------------------------------------

const XMLCh * getDSIGLocalName(const DOMNode *node) {

	if (!strEquals(node->getNamespaceURI(), DSIGConstants::s_unicodeStrURIDSIG))
		return NULL; //DOMString("");
	else
		return node->getLocalName();

}

const XMLCh * getDSIG11LocalName(const DOMNode *node) {

	if (!strEquals(node->getNamespaceURI(), DSIGConstants::s_unicodeStrURIDSIG11))
		return NULL; //DOMString("");
	else
		return node->getLocalName();

}

const XMLCh * getECLocalName(const DOMNode * node) {

	// Exclusive Canonicalisation namespace
	// Probably should have a generic function

	if (!strEquals(node->getNamespaceURI(), DSIGConstants::s_unicodeStrURIEC))
		return NULL;
	else
		return node->getLocalName();

}

const XMLCh * getXPFLocalName(const DOMNode * node) {

	// XPath Filter namespace

	if (!strEquals(node->getNamespaceURI(), DSIGConstants::s_unicodeStrURIXPF))
		return NULL;
	else
		return node->getLocalName();

}

const XMLCh * getXENCLocalName(const DOMNode *node) {

	// XML Encryption namespace node

	if (!strEquals(node->getNamespaceURI(), DSIGConstants::s_unicodeStrURIXENC))
		return NULL;
	else
		return node->getLocalName();

}

const XMLCh * getXENC11LocalName(const DOMNode *node) {

	// XML Encryption 1.1 namespace node

	if (!strEquals(node->getNamespaceURI(), DSIGConstants::s_unicodeStrURIXENC11))
		return NULL;
	else
		return node->getLocalName();

}

#ifdef XSEC_XKMS_ENABLED
const XMLCh * getXKMSLocalName(const DOMNode *node) {

	// XKMS namespace node

	if (!strEquals(node->getNamespaceURI(), XKMSConstants::s_unicodeStrURIXKMS))
		return NULL;
	else
		return node->getLocalName();

}
#endif

// --------------------------------------------------------------------------------
//           Find a nominated DSIG node in a document
// --------------------------------------------------------------------------------

DOMNode *findDSIGNode(DOMNode *n, const char * nodeName) {

	const XMLCh * name = getDSIGLocalName(n);

	if (strEquals(name, nodeName)) {

		return n;

	}

	DOMNode *child = n->getFirstChild();

	while (child != NULL) {

		DOMNode *ret = findDSIGNode(child, nodeName);
		if (ret != NULL)
			return ret;
		child = child->getNextSibling();

	}

	return child;

}

// --------------------------------------------------------------------------------
//           Find a nominated XENC node in a document
// --------------------------------------------------------------------------------

DOMNode *findXENCNode(DOMNode *n, const char * nodeName) {

	const XMLCh * name = getXENCLocalName(n);

	if (strEquals(name, nodeName)) {

		return n;

	}

	DOMNode *child = n->getFirstChild();

	while (child != NULL) {

		DOMNode *ret = findXENCNode(child, nodeName);
		if (ret != NULL)
			return ret;
		child = child->getNextSibling();

	}

	return child;

}

// --------------------------------------------------------------------------------
//           Find particular type of node child
// --------------------------------------------------------------------------------

DOMNode *findFirstChildOfType(DOMNode *n, DOMNode::NodeType t) {

	DOMNode *c;

	if (n == NULL)
		return n;

	c = n->getFirstChild();

	while (c != NULL && c->getNodeType() != t)
		c = c->getNextSibling();

	return c;

}

DOMNode * findNextChildOfType(DOMNode *n, DOMNode::NodeType t) {

	DOMNode * s = n;

	if (s == NULL)
		return s;

	do {
		s = s->getNextSibling();
	} while (s != NULL && s->getNodeType() != t);

	return s;

}

DOMElement *findFirstElementChild(DOMNode *n) {

	DOMNode *c;

	if (n == NULL)
		return NULL;

	c = n->getFirstChild();

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

	return (DOMElement *) c;

}

DOMElement * findNextElementChild(DOMNode *n) {

	DOMNode * s = n;

	if (s == NULL)
		return NULL;

	do {
		s = s->getNextSibling();
	} while (s != NULL && s->getNodeType() != DOMNode::ELEMENT_NODE);

	return (DOMElement *) s;

}

// --------------------------------------------------------------------------------
//           Make a QName
// --------------------------------------------------------------------------------

safeBuffer &makeQName(safeBuffer & qname, safeBuffer &prefix, const char * localName) {

	if (prefix[0] == '\0') {
		qname = localName;
	}
	else {
		qname = prefix;
		qname.sbStrcatIn(":");
		qname.sbStrcatIn(localName);
	}

	return qname;

}
safeBuffer &makeQName(safeBuffer & qname, const XMLCh *prefix, const char * localName) {

	if (prefix == NULL || prefix[0] == 0) {
		qname.sbTranscodeIn(localName);
	}
	else {
		qname.sbXMLChIn(prefix);
		qname.sbXMLChAppendCh(XERCES_CPP_NAMESPACE_QUALIFIER chColon);
		qname.sbXMLChCat(localName);	// Will transcode
	}

	return qname;
}

safeBuffer &makeQName(safeBuffer & qname, const XMLCh *prefix, const XMLCh * localName) {

	if (prefix == NULL || prefix[0] == 0) {
		qname.sbXMLChIn(localName);
	}
	else {
		qname.sbXMLChIn(prefix);
		qname.sbXMLChAppendCh(XERCES_CPP_NAMESPACE_QUALIFIER chColon);
		qname.sbXMLChCat(localName);
	}

	return qname;
}

// --------------------------------------------------------------------------------
//           "Quick" Transcode (low performance)
// --------------------------------------------------------------------------------



XMLT::XMLT(const char * str) {

	mp_unicodeStr = XMLString::transcode(str);

}

XMLT::~XMLT (void) {

	XSEC_RELEASE_XMLCH(mp_unicodeStr);

}

XMLCh * XMLT::getUnicodeStr(void) {

	return mp_unicodeStr;

}

// --------------------------------------------------------------------------------
//           Gather text from children
// --------------------------------------------------------------------------------

void gatherChildrenText(DOMNode * parent, safeBuffer &output) {

	DOMNode * c = parent->getFirstChild();

	output.sbXMLChIn(DSIGConstants::s_unicodeStrEmpty);

	while (c != NULL) {

		if (c->getNodeType() == DOMNode::TEXT_NODE)
			output.sbXMLChCat(c->getNodeValue());

		c = c->getNextSibling();

	}

}

// --------------------------------------------------------------------------------
//           Some UTF8 utilities
// --------------------------------------------------------------------------------

XMLCh * transcodeFromUTF8(const unsigned char * src) {

	// Take a UTF-8 buffer and transcode to UTF-16

	safeBuffer fullDest;
	fullDest.sbXMLChIn(DSIGConstants::s_unicodeStrEmpty);
	XMLCh outputBuf[2050];

	// Used to record byte sizes
	unsigned char charSizes[2050];

	// Grab a transcoder
	XMLTransService::Codes failReason;

	XMLTranscoder* t =
		XMLPlatformUtils::fgTransService->makeNewTranscoderFor("UTF-8",
					   failReason,
					   2*1024,
					   XMLPlatformUtils::fgMemoryManager);
	Janitor<XMLTranscoder> j_t(t);

	// Need to loop through, 2K at a time
	XMLSize_t bytesEaten, bytesEatenCounter;
	XMLSize_t charactersEaten;
	XMLSize_t totalBytesEaten = 0;
	XMLSize_t bytesToEat = XMLString::stringLen((char *) src);

	while (totalBytesEaten < bytesToEat) {

	    XMLSize_t toEat = bytesToEat - totalBytesEaten;


		if (toEat > 2048)
			toEat = 2048;

		t->transcodeFrom(&src[totalBytesEaten],
						toEat,
						outputBuf,
						2048,
						bytesEaten,
						charSizes);

		// Determine how many characters were used
		bytesEatenCounter = 0;
		charactersEaten = 0;
		while (bytesEatenCounter < bytesEaten) {
			bytesEatenCounter += charSizes[charactersEaten++];
		}

		outputBuf[charactersEaten] = chNull;
		fullDest.sbXMLChCat(outputBuf);
		totalBytesEaten += bytesEaten;
	}

	// Dup and output
	return XMLString::replicate(fullDest.rawXMLChBuffer());

}

char * transcodeToUTF8(const XMLCh * src) {

	// Take a UTF-16 buffer and transcode to UTF-8

	safeBuffer fullDest("");
	unsigned char outputBuf[2050];

	// Grab a transcoder
	XMLTransService::Codes failReason;

	XMLTranscoder* t =
		XMLPlatformUtils::fgTransService->makeNewTranscoderFor("UTF-8",
						   failReason,
						   2*1024,
						   XMLPlatformUtils::fgMemoryManager);
	Janitor<XMLTranscoder> j_t(t);

	// Need to loop through, 2K at a time
	XMLSize_t charactersEaten, charactersOutput;
	XMLSize_t totalCharsEaten = 0;
	XMLSize_t charsToEat = XMLString::stringLen(src);

	while (totalCharsEaten < charsToEat) {

		XMLSize_t toEat = charsToEat - totalCharsEaten;

		if (toEat > 2048)
			toEat = 2048;

		charactersOutput = t->transcodeTo(&src[totalCharsEaten],
						toEat,
						(unsigned char * const) outputBuf,
						2048,
						charactersEaten,
						XMLTranscoder::UnRep_RepChar);

		outputBuf[charactersOutput] = '\0';
		fullDest.sbStrcatIn((char *) outputBuf);
		totalCharsEaten += charactersEaten;
	}

	// Dup and output
	return XMLString::replicate(fullDest.rawCharBuffer());

}

// --------------------------------------------------------------------------------
//           String decode/encode
// --------------------------------------------------------------------------------

/*
 * Distinguished names have a particular encoding that needs to be performed prior
 * to enclusion in the DOM
 */

XMLCh * encodeDName(const XMLCh * toEncode) {

	XERCES_CPP_NAMESPACE_USE;

	safeBuffer result;

	static XMLCh s_strEncodedSpace[] = {
		chBackSlash,
		chDigit_2,
		chDigit_0,
		chNull
	};

	result.sbXMLChIn(DSIGConstants::s_unicodeStrEmpty);

	if (toEncode == NULL) {
		return NULL;
	}


	// Find where the trailing whitespace starts
	const XMLCh * ws = &toEncode[XMLString::stringLen(toEncode)];

	ws--;
	while (ws != toEncode &&
		(*ws == '\t' || *ws == '\r' || *ws ==' ' || *ws == '\n'))
		ws--;

	// Set to first white space character, if we didn't get back to the start
	if (toEncode != ws)
		ws++;

	// Now run through each character and encode if necessary

	const XMLCh * i = toEncode;

	if (*i == chPound) {
		// "#" Characters escaped at the start of a string
		result.sbXMLChAppendCh(chBackSlash);
	}

	while (*i != chNull && i != ws) {

		if (*i <= 0x09) {
			result.sbXMLChAppendCh(chBackSlash);
			result.sbXMLChAppendCh(chDigit_0);
			result.sbXMLChAppendCh(chDigit_0 + *i);
		}
		else if (*i <= 0x0f) {
			result.sbXMLChAppendCh(chBackSlash);
			result.sbXMLChAppendCh(chDigit_0);
			result.sbXMLChAppendCh(chLatin_A + *i);
		}
		else if (*i <= 0x19) {
			result.sbXMLChAppendCh(chBackSlash);
			result.sbXMLChAppendCh(chDigit_1);
			result.sbXMLChAppendCh(chDigit_0 + *i);
		}
		else if (*i <= 0x1f) {
			result.sbXMLChAppendCh(chBackSlash);
			result.sbXMLChAppendCh(chDigit_1);
			result.sbXMLChAppendCh(chLatin_A + *i);
		}

		else if (*i == chComma) {

			// Determine if this is an RDN separator
			const XMLCh *j = i;
			j++;
			while (*j != chComma && *j != chEqual && *j != chNull)
				j++;

			if (*j != chEqual)
				result.sbXMLChAppendCh(chBackSlash);

			result.sbXMLChAppendCh(*i);

		}

		else {

			if (*i == chPlus ||
				*i == chDoubleQuote ||
				*i == chBackSlash ||
				*i == chOpenAngle ||
				*i == chCloseAngle ||
				*i == chSemiColon) {

				result.sbXMLChAppendCh(chBackSlash);
			}

			result.sbXMLChAppendCh(*i);

		}

		i++;

	}

	// Now encode trailing white space
	while (*i != chNull) {

		if (*i == ' ')
			result.sbXMLChCat(s_strEncodedSpace);
		else
			result.sbXMLChAppendCh(*i);

		i++;

	}

	return XMLString::replicate(result.rawXMLChBuffer());

}

XMLCh * decodeDName(const XMLCh * toDecode) {

	// Take an encoded name and decode to a normal XMLCh string

	XERCES_CPP_NAMESPACE_USE;

	safeBuffer result;

	result.sbXMLChIn(DSIGConstants::s_unicodeStrEmpty);

	if (toDecode == NULL) {
		return NULL;
	}

	const XMLCh * i = toDecode;

	if (*i == chBackSlash && i[1] == chPound) {

		result.sbXMLChAppendCh(chPound);
		i++;
		i++;

	}

	while (*i != chNull) {

		if (*i == chBackSlash) {

			i++;

			if (*i == chDigit_0) {

				i++;

				if (*i >= chDigit_0 && *i <= chDigit_9) {
					result.sbXMLChAppendCh(*i - chDigit_0);
				}
				else if (*i >= chLatin_A && *i <= chLatin_F) {
					result.sbXMLChAppendCh(10 + *i - chLatin_A);
				}
				else if (*i >= chLatin_a && *i <= chLatin_f) {
					result.sbXMLChAppendCh(10 + *i - chLatin_a);
				}
				else {
					throw XSECException(XSECException::DNameDecodeError,
						"Unexpected escaped character in Distinguished name");
				}
			}

			else if (*i == chDigit_1) {

				i++;

				if (*i >= chDigit_0 && *i <= chDigit_9) {
					result.sbXMLChAppendCh(16 + *i - chDigit_0);
				}
				else if (*i >= chLatin_A && *i <= chLatin_F) {
					result.sbXMLChAppendCh(26 + *i - chLatin_A);
				}
				else if (*i >= chLatin_a && *i <= chLatin_f) {
					result.sbXMLChAppendCh(26 + *i - chLatin_a);
				}
				else {
					throw XSECException(XSECException::DNameDecodeError,
						"Unexpected escaped character in Distinguished name");
				}
			}

			else if (*i == chDigit_2) {

				i++;

				if (*i == '0') {
					result.sbXMLChAppendCh(' ');
				}

				else {
					throw XSECException(XSECException::DNameDecodeError,
						"Unexpected escaped character in Distinguished name");
				}

			}

			else if (*i == chComma ||
					 *i == chPlus ||
					 *i == chDoubleQuote ||
					 *i == chBackSlash ||
					 *i == chOpenAngle ||
					 *i == chCloseAngle ||
					 *i == chSemiColon) {

				result.sbXMLChAppendCh(*i);
			}

			else {

				throw XSECException(XSECException::DNameDecodeError,
					"Unexpected escaped character in Distinguished name");

			}

			i++;

		}

		else {

			result.sbXMLChAppendCh(*i++);

		}

	}

	return XMLString::replicate(result.rawXMLChBuffer());

}

// --------------------------------------------------------------------------------
//           Misc string functions
// --------------------------------------------------------------------------------

// These three functions are pretty much lifted from XMLURL.cpp in Xerces

static bool isHexDigit(const XMLCh toCheck)
{
    if ((toCheck >= chDigit_0 && toCheck <= chDigit_9)
    ||  (toCheck >= chLatin_A && toCheck <= chLatin_F)
    ||  (toCheck >= chLatin_a && toCheck <= chLatin_f))
    {
        return true;
    }
    return false;
}

static unsigned int xlatHexDigit(const XMLCh toXlat)
{
	if (!isHexDigit(toXlat)) {
		throw XSECException(XSECException::ErrorOpeningURI,
			"Unknown hex char");
	}
    if ((toXlat >= chDigit_0) && (toXlat <= chDigit_9))
        return (unsigned int)(toXlat - chDigit_0);

    if ((toXlat >= chLatin_A) && (toXlat <= chLatin_F))
        return (unsigned int)(toXlat - chLatin_A) + 10;

    return (unsigned int)(toXlat - chLatin_a) + 10;
}

XMLCh * cleanURIEscapes(const XMLCh * uriPath) {

    XMLByte *ptr, *utf8Path;
    XMLSize_t len = XMLString::stringLen(uriPath);

    ptr = utf8Path = new XMLByte[len + 1];

    for (XMLSize_t i = 0; i < len; i++) {
        unsigned int value = uriPath[i];

        if (value > 255) {
            delete[] utf8Path;
            throw XSECException(XSECException::ErrorOpeningURI, "Wide character in URI");
        }

        if (value == chPercent) {
            if (!(i + 2 < len && isHexDigit(uriPath[i + 1]) && isHexDigit(uriPath[i + 2]))) {
                delete[] utf8Path;
                throw XSECException(XSECException::ErrorOpeningURI, "Bad escape sequence in URI");
            }

            value = (xlatHexDigit(uriPath[i + 1]) * 16) + (xlatHexDigit(uriPath[i + 2]));
            i += 2;
        }

        *(ptr++) = (XMLByte) value;
    }
    *ptr = 0;

    try {
        XMLCh* unicodePath = transcodeFromUTF8(utf8Path);
        delete[] utf8Path;
        return unicodePath;
    }
    catch (const XMLException&) {
    }

    delete[] utf8Path;
    throw XSECException(XSECException::ErrorOpeningURI, "Failed to transcode URI from UTF-8");
}

// --------------------------------------------------------------------------------
//           Generate Ids
// --------------------------------------------------------------------------------

void makeHexByte(XMLCh * h, unsigned char b) {

	unsigned char toConvert =  (b & 0xf0);
	toConvert = (toConvert >> 4);

	if (toConvert < 10)
		h[0] = chDigit_0 + toConvert;
	else
		h[0] = chLatin_a + toConvert - 10;

	toConvert =  (b & 0xf);

	if (toConvert < 10)
		h[1] = chDigit_0 + toConvert;
	else
		h[1] = chLatin_a + toConvert - 10;

}


XMLCh * generateId(unsigned int bytes) {

	unsigned char b[128];
	XMLCh id[258];
	unsigned int toGen = (bytes > 128 ? 16 : bytes);

	// Get the appropriate amount of random data
	// Need to zeroise to ensure valgrind is happy
	memset(b, 0, 128);
	memset(id, 0, sizeof(id));
	if (XSECPlatformUtils::g_cryptoProvider->getRandom(b, toGen) != toGen) {

		throw XSECException(XSECException::CryptoProviderError,
			"generateId - could not obtain enough random");

	}

	id[0] = chLatin_I;

	unsigned int i;
	for (i = 0; i < toGen; ++i) {

		makeHexByte(&id[1+(i*2)], b[i]);

	}

	id[1+(i*2)] = chNull;

	return XMLString::replicate(id);

}

