/**
 * 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
 *
 * XSECSafeBuffer := a class for storing expanding amounts of information.
 *
 * Author(s): Berin Lautenbach
 *
 * $Id$
 *
 */

// XSEC includes

#include <xsec/utils/XSECSafeBuffer.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/transformers/TXFMBase.hpp>

#include "XSECDOMUtils.hpp"

#include <xercesc/util/XMLUniDefs.hpp>

XSEC_USING_XERCES(XMLString);

// Standard includes

#include <stdlib.h>
#include <string.h>

size_t safeBuffer::size_XMLCh;

#if defined (_MSC_VER)
#pragma warning(disable: 4311)
#endif

void safeBuffer::checkAndExpand(XMLSize_t size) {

	// For a given size, check it will fit (with one byte spare)
	// and expand if necessary

	if (bufferSize >= 2 && size < bufferSize - 2) {
		return;
	}

	if (size > XERCES_SIZE_MAX - DEFAULT_SAFE_BUFFER_SIZE) {
		/* We've got a string that's too big to deal with */
		throw XSECException(XSECException::SafeBufferError,
			"Buffer has grown too large");
	}

	// Resize and add 1K for further growth
	XMLSize_t newBufferSize = size + DEFAULT_SAFE_BUFFER_SIZE;

	unsigned char * newBuffer = new unsigned char[newBufferSize];
	if (newBuffer == NULL)
	{
		/* Ran out of memory */
		throw XSECException(XSECException::MemoryAllocationFail,
			"Error allocating memory for Buffer");
	}

	memset((void *) newBuffer, 0, newBufferSize);
	memcpy(newBuffer, buffer, bufferSize);

	// If we are sensitive, clean the old buffer
	if (m_isSensitive == true)
		cleanseBuffer();

	// clean up
	bufferSize = newBufferSize;
	delete[] buffer;
	buffer = newBuffer;
}

void safeBuffer::checkBufferType(bufferType bt) const {

	if (bt != m_bufferType) {

		throw XSECException(XSECException::SafeBufferError,
			"Attempt to perform an operation on a buffer of incorrect type");
	}

}


void safeBuffer::setBufferType(bufferType bt) {

	m_bufferType = bt;

}

void safeBuffer::resize(XMLSize_t sz) {

	checkAndExpand(sz);

}

safeBuffer::safeBuffer(XMLSize_t initialSize) {

	// Initialise the buffer with a set size string

	bufferSize = initialSize;
	buffer = new unsigned char[initialSize];
	memset((void *) buffer, 0, bufferSize);
	mp_XMLCh = NULL;
	m_bufferType = BUFFER_UNKNOWN;
	m_isSensitive = false;

}

safeBuffer::safeBuffer() {

	bufferSize = DEFAULT_SAFE_BUFFER_SIZE;
	buffer = new unsigned char[bufferSize];
	memset((void *) buffer, 0, bufferSize);
	mp_XMLCh = NULL;
	m_bufferType = BUFFER_UNKNOWN;
	m_isSensitive = false;

}

safeBuffer::safeBuffer(const char * inStr, XMLSize_t initialSize) {
    // Initialise with a string
    bufferSize = ((XMLSize_t) strlen(inStr) > initialSize ? (XMLSize_t) (strlen(inStr) * 2) : initialSize);

	buffer = new unsigned char[bufferSize];
	memset((void *) buffer, 0, bufferSize);
	strcpy((char *) buffer, inStr);
	mp_XMLCh = NULL;
	m_bufferType = BUFFER_CHAR;
	m_isSensitive = false;

}

safeBuffer::safeBuffer(const safeBuffer & other) {

	// Copy constructor

	bufferSize = other.bufferSize;
	buffer = new unsigned char [bufferSize];

	memcpy(buffer, other.buffer, bufferSize);

	if (other.mp_XMLCh != NULL) {

		mp_XMLCh = XMLString::replicate(other.mp_XMLCh);

	}
	else {

		mp_XMLCh = NULL;

	}

	m_bufferType = other.m_bufferType;
	m_isSensitive = other.m_isSensitive;

}

safeBuffer::~safeBuffer() {


	if (buffer != NULL) {
		if (m_isSensitive == true)
			cleanseBuffer();
		delete[] buffer;
	}

	if (mp_XMLCh != NULL)
		XSEC_RELEASE_XMLCH(mp_XMLCh);

}

void safeBuffer::init (void) {

	size_XMLCh = sizeof(XMLCh);

}

// "IN" functions - these read in information to the buffer

void safeBuffer::sbStrcpyIn(const char * inStr) {

	// Copy a string into the safe buffer
    checkAndExpand((XMLSize_t) strlen(inStr));
	strcpy((char *) buffer, inStr);
	m_bufferType = BUFFER_CHAR;

}

void safeBuffer::sbStrcpyIn(const safeBuffer & inStr) {

	inStr.checkBufferType(BUFFER_CHAR);
    checkAndExpand((XMLSize_t) strlen((char *) inStr.buffer));
	strcpy((char *) buffer, (char *) inStr.buffer);
	m_bufferType = BUFFER_CHAR;

}


void safeBuffer::sbStrncpyIn(const char * inStr, XMLSize_t n) {
    XMLSize_t len = (XMLSize_t) strlen(inStr);
	checkAndExpand((n < len) ? n : len);
	strncpy((char *) buffer, inStr, n);
	buffer[n] = '\0';
	m_bufferType = BUFFER_CHAR;

}

void safeBuffer::sbStrncpyIn(const safeBuffer & inStr, XMLSize_t n) {

	inStr.checkBufferType(BUFFER_CHAR);
	checkAndExpand(n);
	strncpy((char *) buffer, (char *) inStr.buffer, n);
	buffer[n] = '\0';
	m_bufferType = BUFFER_CHAR;


}


void safeBuffer::sbStrcatIn(const char * inStr) {

	checkBufferType(BUFFER_CHAR);
    checkAndExpand((XMLSize_t) (strlen((char *) buffer) + strlen(inStr) + 1));
	strcat((char *) buffer, inStr);

}

void safeBuffer::sbStrcatIn(const safeBuffer & inStr) {

	checkBufferType(BUFFER_CHAR);
    checkAndExpand((XMLSize_t) (strlen((char *) buffer) + strlen((char *) inStr.buffer) + 2));
	strcat((char *) buffer, (char *) inStr.buffer);

}

void safeBuffer::sbStrncatIn(const char * inStr, XMLSize_t n) {
    checkBufferType(BUFFER_CHAR);
    XMLSize_t len = (XMLSize_t) strlen(inStr);
	XMLSize_t totalLen = ((n < len) ? n : len) + (XMLSize_t)strlen((char *)buffer);
    checkAndExpand(totalLen + 2);
	strncat((char *) buffer, inStr, n);
	buffer[totalLen] = '\0';

}

void safeBuffer::sbMemcpyIn(const void * inBuf, XMLSize_t n) {

	checkAndExpand(n);
	memcpy(buffer, inBuf, n);
	m_bufferType = BUFFER_UNKNOWN;

}

void safeBuffer::sbMemcpyIn(XMLSize_t offset, const void * inBuf, XMLSize_t n) {

	checkAndExpand(n + offset);
	memcpy(&buffer[offset], inBuf, n);
	m_bufferType = BUFFER_UNKNOWN;
}

void safeBuffer::sbStrinsIn(const char * inStr, XMLSize_t offset) {

    checkBufferType(BUFFER_CHAR);

    XMLSize_t bl = (XMLSize_t) strlen((char *) buffer);
    XMLSize_t il = (XMLSize_t) strlen((char *) inStr);

	if (offset > bl) {
		throw XSECException(XSECException::SafeBufferError,
			"Attempt to insert string after termination point");
	}

	checkAndExpand(bl + il + 1);

	memmove(&buffer[offset + il], &buffer[offset], bl - offset + 1);
	memcpy(&buffer[offset], inStr, il);

}

void safeBuffer::sbMemcpyOut(void *outBuf, XMLSize_t n) const {

	// WARNING - JUST ASSUMES OUTPUT BUFFER LONG ENOUGH
	// ALSO MAKES NO ASSUMPTION OF THE BUFFER TYPE

	if (n > bufferSize) {
		throw XSECException(XSECException::SafeBufferError,
			"safeBuffer::sbMemcpyOut Attempt to copy more data than buffer can hold");
	}

	memcpy(outBuf, buffer, n);

}

void safeBuffer::sbMemshift(XMLSize_t toOffset, XMLSize_t fromOffset, XMLSize_t len) {

	// Move data in the buffer around
	checkAndExpand(len + (toOffset > fromOffset ? toOffset : fromOffset));

	memmove(&buffer[toOffset], &buffer[fromOffset], len);

}


// Comparisons

int safeBuffer::sbStrncmp(const char *inStr, XMLSize_t n) const {

	checkBufferType(BUFFER_CHAR);
	return (strncmp((char *) buffer, inStr, n));

}

int safeBuffer::sbStrcmp(const char *inStr) const {

	checkBufferType(BUFFER_CHAR);
	return (strcmp((char *) buffer, inStr));

}

int safeBuffer::sbStrcmp(const safeBuffer & inStr) const {

	checkBufferType(BUFFER_CHAR);
	return (strcmp((char *) buffer, (char *) inStr.buffer));

}

int safeBuffer::sbOffsetStrcmp(const char * inStr, XMLSize_t offset) const {

    checkBufferType(BUFFER_CHAR);
    XMLSize_t bl = (XMLSize_t) strlen((char *) buffer);

	if (offset > bl)
		return -1;

	return (strcmp((char *) &buffer[offset], inStr));

}

XMLSSize_t safeBuffer::sbStrstr(const char * inStr) const {

	checkBufferType(BUFFER_CHAR);
	const char* p = strstr((char *) buffer, inStr);

	if (p == NULL)
		return -1;

	XMLSSize_t d = p - (char*) buffer;
	if (d > bufferSize || d < 0)
		return -1;

	return d;

}

XMLSSize_t safeBuffer::sbOffsetStrstr(const char * inStr, XMLSize_t offset) const {

	checkBufferType(BUFFER_CHAR);
	XMLSize_t bl = strlen((char *) buffer);

	if (offset > bl)
		return -1;

	const char* p = strstr((char *) &buffer[offset], inStr);

	if (p == NULL)
		return -1;

	XMLSSize_t d = p - (char*) buffer;
	if (d > bufferSize || d < 0)
		return -1;

	return d;

}

// XMLCh and char common functions

void safeBuffer::sbStrlwr(void) {

	if (m_bufferType == BUFFER_UNKNOWN) {

		throw XSECException(XSECException::SafeBufferError,
			"Attempt to perform an operation on a buffer of incorrect type");

	}

	if (m_bufferType == BUFFER_CHAR) {

	    XMLSize_t i, l = (XMLSize_t) strlen((char *) buffer);

		for (i = 0; i < l; ++i) {
			if (buffer[i] >= 'A' && buffer[i] <= 'Z')
				buffer[i] = (buffer[i] - 'A') + 'a';
		}

	}

	else {

		XMLCh * b = (XMLCh *) buffer;
		XMLSize_t i, l = XMLString::stringLen(b);

		for (i = 0; i < l; ++i) {
			if (b[i] >= XERCES_CPP_NAMESPACE_QUALIFIER chLatin_A && b[i] <= XERCES_CPP_NAMESPACE_QUALIFIER chLatin_Z)
				b[i] = (b[i] - XERCES_CPP_NAMESPACE_QUALIFIER chLatin_A) + XERCES_CPP_NAMESPACE_QUALIFIER chLatin_a;
		}

	}

}
// Operators

unsigned char & safeBuffer::operator[](XMLSize_t n) {

	// If the character is outside our range (but +ve), then simply increase
	// the buffer size - NOTE: it is not our problem if the caller does
	// not realise they are outside the buffer, we are simply trying to ensure
	// the call is "safe"

	checkAndExpand(n);

	return buffer[n];

}

safeBuffer & safeBuffer::operator= (const safeBuffer & cpy) {

	if (bufferSize != cpy.bufferSize) {

		if (bufferSize != 0) {

			if (m_isSensitive == true)
				cleanseBuffer();

			delete [] buffer;
		}

		buffer = new unsigned char [cpy.bufferSize];
		bufferSize = cpy.bufferSize;

	}

	memcpy(buffer, cpy.buffer, bufferSize);
	m_bufferType = cpy.m_bufferType;
	// Once we are sensitive, we are always sensitive
	m_isSensitive = m_isSensitive || cpy.m_isSensitive;

	return *this;
}

safeBuffer & safeBuffer::operator= (const XMLCh * inStr) {

	checkAndExpand(XMLString::stringLen(inStr) * size_XMLCh);
	XMLString::copyString((XMLCh *) buffer, inStr);
	m_bufferType = BUFFER_UNICODE;
	return *this;

}

safeBuffer & safeBuffer::operator << (TXFMBase * t) {

	// Read into buffer the output of the transform
	XMLSize_t offset = 0;
	unsigned char inBuf[2048];
	XMLSize_t bytesRead;

	while ((bytesRead = t->readBytes(inBuf, 2000)) > 0) {

		checkAndExpand(offset + bytesRead + 1);
		memcpy(&buffer[offset], inBuf, bytesRead);
		offset += bytesRead;

	}

	m_bufferType = BUFFER_CHAR;
	buffer[offset] = '\0';

	return *this;
}


// Unicode Functions

const XMLCh * safeBuffer::sbStrToXMLCh(void) const {

	checkBufferType(BUFFER_CHAR);
	if (mp_XMLCh != NULL)
		XSEC_RELEASE_XMLCH(mp_XMLCh);

	mp_XMLCh = XMLString::transcode((char *) buffer);

	return mp_XMLCh;

}

void safeBuffer::sbTranscodeIn(const XMLCh * inStr) {

	// Transcode the string to the local code page and store in the buffer
	char * t;

	t = XMLString::transcode(inStr);

	assert (t != 0);


	// Now copy into our local buffer - a bit inefficient but better in the long run
	// as a buffer that is the exact size is likely to be deleted anyway during a
	// concat operation

	XMLSize_t len = (XMLSize_t) strlen(t) + 1;
	checkAndExpand(len);
	strcpy((char *) buffer, t);
	m_bufferType = BUFFER_CHAR;

	XSEC_RELEASE_XMLCH(t);

}

void safeBuffer::sbTranscodeIn(const char * inStr) {

	// Transcode the string to the local code page and store in the buffer
	XMLCh * t;

	t = XMLString::transcode(inStr);

	assert (t != 0);

	// Copy into local buffer

	XMLSize_t len = XMLString::stringLen(t) + 1;
    len *= (XMLSize_t) size_XMLCh;
	checkAndExpand(len);

	XMLString::copyString((XMLCh *) buffer, t);
	m_bufferType = BUFFER_UNICODE;

	XSEC_RELEASE_XMLCH(t);

}


void safeBuffer::sbXMLChIn(const XMLCh * in) {

	checkAndExpand((XMLString::stringLen(in) + 1) * size_XMLCh);

	XMLString::copyString((XMLCh *) buffer, in);
	m_bufferType = BUFFER_UNICODE;

}

void safeBuffer::sbXMLChAppendCh(const XMLCh c) {

	checkBufferType(BUFFER_UNICODE);
	XMLSize_t len = XMLString::stringLen((XMLCh *) buffer);

	checkAndExpand((len + 2) * size_XMLCh);

	((XMLCh *) buffer)[len++] = c;
	((XMLCh *) buffer)[len] = 0;

}

void safeBuffer::sbXMLChCat(const XMLCh *str) {

	checkBufferType(BUFFER_UNICODE);
	XMLSize_t len = XMLString::stringLen((XMLCh *) buffer) * size_XMLCh;
	len += XMLString::stringLen(str) * size_XMLCh;
	len += (2 * ((XMLSize_t) size_XMLCh));

	checkAndExpand(len);

	XMLString::catString((XMLCh *) buffer, str);

}

void safeBuffer::sbXMLChCat(const char * str) {

	checkBufferType(BUFFER_UNICODE);
	XMLSize_t len = XMLString::stringLen((XMLCh *) buffer) * size_XMLCh;

	XMLCh * t = XMLString::transcode(str);

	assert (t != NULL);

	len += XMLString::stringLen(t) * size_XMLCh;
	len += (XMLSize_t) (2 * size_XMLCh);

	checkAndExpand(len);

	XMLString::catString((XMLCh *) buffer, t);

	XSEC_RELEASE_XMLCH(t);
}

void safeBuffer::sbXMLChCat8(const char * str) {

	checkBufferType(BUFFER_UNICODE);

	XMLCh * toAdd = transcodeFromUTF8((const unsigned char *) str);
	sbXMLChCat(toAdd);
	XSEC_RELEASE_XMLCH(toAdd);

}

// Get functions

XMLSize_t safeBuffer::sbStrlen(void) const {

    checkBufferType(BUFFER_CHAR);
    return (XMLSize_t) (strlen ((char *) buffer));

}

XMLSize_t safeBuffer::sbRawBufferSize(void) const {

    return bufferSize;

}


// raw buffer manipulation

const unsigned char * safeBuffer::rawBuffer() const {

	return buffer;

}

const char * safeBuffer::rawCharBuffer() const {

	return (char *) buffer;

}

const XMLCh * safeBuffer::rawXMLChBuffer() const {

	return (XMLCh *) buffer;

}

// Sensitive data functions

void safeBuffer::isSensitive(void) {

	m_isSensitive = true;

}

void safeBuffer::cleanseBuffer(void) {

	// Cleanse the main buffer
	for (XMLSize_t i = 0; i < bufferSize; ++i)
		buffer[i] = 0;

}
