// Base64.cpp: implementation of the WMBase64 class.
//
//////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000 Robert Cunnings. All rights reserved.
//
// You may copy, modify, distribute or publish this code free of charge.
//
// NO WARRANTY: This software is provided AS IS, without warranty of any kind.
//
//////////////////////////////////////////////////////////////////////
//
// Class used to base64 encode and decode binary data.
// 
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Base64.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

WMBase64::WMBase64()
{
	// fill encoding and decoding arrays
	memset(DecodeArray, 0xFF, sizeof(DecodeArray));
	for (int i = 0; i < 64; i++)
	{
		int nValue;
		if (i < 26)
			nValue = i + 'A';
		else if (i < 52)
			nValue = (i - 26) + 'a';
		else if (i < 62)
			nValue = (i - 52) + '0';
		else if (i == 62)
			nValue = '+';
		else
			nValue = '/';

		EncodeArray[i] = (unsigned char)nValue;
		DecodeArray[nValue] = i;
	}
	// make entry for padding character
	DecodeArray['='] = PADDING;
}

WMBase64::~WMBase64()
{

}

bool WMBase64::Encode(const unsigned char *pszUnencoded, int nLength, string& strEncoded)
{
	int nTriplets = nLength / 3;
	int nRemainder = nLength % 3;
	// set up output buffer - assume worst case...4 for remainder and 1 for null == 5 extra
	char *pszOutBuffer = (char *)malloc((nTriplets * 4) + 5);
	// set up pointer for traversing buffers
	const unsigned char *pIn = pszUnencoded;
	char *pOut = pszOutBuffer;
	// encode triplets
	for (int i = 0; i < nTriplets; i++)
	{
		// encode triplet
		unsigned char c1 = pIn[0];
		unsigned char c2 = pIn[1];
		unsigned char c3 = pIn[2];
		
		*pOut++ = EncodeArray[(c1 >> 2)];
		*pOut++ = EncodeArray[((c1 & 3) << 4) | (c2 >> 4)];
		*pOut++ = EncodeArray[((c2 & 15) << 2) | (c3 >> 6)];
		*pOut++ = EncodeArray[(c3 & 63)];

		pIn = pIn + 3;
	}
	// deal with any remainders
	if (nRemainder == 2)
	{
		// encode last 2 bytes
		unsigned char c1 = pIn[0];
		unsigned char c2 = pIn[1];
		
		*pOut++ = EncodeArray[(c1 >> 2)];
		*pOut++ = EncodeArray[((c1 & 3) << 4) | (c2 >> 4)];
		*pOut++ = EncodeArray[((c2 & 15) << 2)];
		*pOut++ = '=';
	}
	if (nRemainder == 1)
	{
		// encode last byte
		unsigned char c1 = pIn[0];
		
		*pOut++ = EncodeArray[(c1 >> 2)];
		*pOut++ = EncodeArray[((c1 & 3) << 4)];
		*pOut++ = '=';
		*pOut++ = '=';
	}
	// null terminate output string
	*pOut = 0;
	// create return string
	strEncoded = pszOutBuffer;
	// clean up
	free(pszOutBuffer);
	// done
	return true;
}

bool WMBase64::Decode(const string& strEncoded, unsigned char *pszDecoded, int& nOutLength)
{
	int nLength = (int)strEncoded.size();
	int nQuads = nLength / 4;
	// make sure length is a multiple of 4
	int nRemainder = nLength % 4;
	if (nRemainder > 0)
	{
		// no good
		return false;
	}
	nOutLength = nQuads * 3;
	if (pszDecoded == NULL)
	{
		// no conversion...only a query for output buffer size
		return true;
	}
	// set up input buffer and copy in string
	char *pszInBuffer = (char *)malloc(nLength + 1);
	strcpy(pszInBuffer, strEncoded.c_str());
	// set up pointer for traversing buffers
	char *pIn = pszInBuffer;
	unsigned char *pOut = pszDecoded;
	// decode quads
	for (int i = 0; i < nQuads; i++)
	{
		// decode quad
		unsigned char c1 = DecodeArray[pIn[0]];
		unsigned char c2 = DecodeArray[pIn[1]];
		unsigned char c3 = DecodeArray[pIn[2]];
		unsigned char c4 = DecodeArray[pIn[3]];
		// check for disallowed characters in input
		if (c1 == 0xFF || c2 == 0xFF || c3 == 0xFF || c4 == 0xFF)
		{
			// no good
			free(pszInBuffer);
			return false;
		}

		// make sure any padding is correctly located
		if (c1 == PADDING || c2 == PADDING ||
					(c3 == PADDING && c4 != PADDING))
		{
			// no good
			free(pszInBuffer);
			return false;
		}
		// first case...no padding
		if (c4 != PADDING)
		{
			*pOut++ = (c1 << 2) | (c2 >> 4);
			*pOut++ = (c2 << 4) | (c3 >> 2);
			*pOut++ = (c3 << 6) | c4;
		}
		// second case...last char is padding
		else if (c3 != PADDING)
		{
			*pOut++ = (c1 << 2) | (c2 >> 4);
			*pOut++ = (c2 << 4) | (c3 >> 2);
			nOutLength--;
		}
		// third case...last two chars are padding
		else
		{
			*pOut++ = (c1 << 2) | (c2 >> 4);
			nOutLength--;
			nOutLength--;
		}
		pIn = pIn + 4;
	}
	// clean up
	free(pszInBuffer);
	// done
	return true;
}

