/* AbiSource Program Utilities
 * Copyright (C) 1998 AbiSource, Inc.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
 * 02111-1307, USA.
 */
 
#include "ut_base64.h"

/*****************************************************************/
/*****************************************************************/

// See RFC1521 for details.

static UT_Byte s_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static UT_Byte s_pad = '=';

static UT_Byte s_inverse(UT_Byte b)
{
	if ((b >= 'A') && (b <= 'Z')) return (     b - 'A');
	if ((b >= 'a') && (b <= 'z')) return (26 + b - 'a');
	if ((b >= '0') && (b <= '9')) return (52 + b - '0');
	if (b == '+') return 62;
	if (b == '/') return 63;

	UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
	return false;
}

/*****************************************************************/
/*****************************************************************/

bool UT_Base64Encode(UT_ByteBuf * pDest, const UT_ByteBuf * pSrc)
{
	// Base64 encode the raw (presumed binary) data in pSrc into pDest.
	// return false if error.

	UT_ASSERT(pDest && pSrc);

	pDest->truncate(0);
	
	UT_uint32 lenSrc = pSrc->getLength();
	if (lenSrc == 0)						// empty source buffer yields empty output buffer.
		return true;

	// compute the amount of space needed in the destination and reserve space for it in advance.
	
	UT_uint32 lenDest = (lenSrc + 2) / 3 * 4;
	if (!pDest->ins(0,lenDest))
		return false;

	UT_uint32 kSrc, kDest;
	const UT_Byte * p = pSrc->getPointer(0);
	for (kSrc=0, kDest=0; (kSrc < lenSrc); kSrc+=3, kDest+=4)
	{
		// encode each group of 3 bytes in the input into 4 bytes in the output.

		UT_Byte s1 = p[kSrc];
		UT_Byte s2 = (kSrc+1 < lenSrc) ? p[kSrc+1] : 0;
		UT_Byte s3 = (kSrc+2 < lenSrc) ? p[kSrc+2] : 0;

		UT_uint32 d = (s1<<16) | (s2<<8) | s3;
		
		UT_Byte dd[4];
		dd[0] =                     s_alphabet[( (d>>18)        )];
		dd[1] =                     s_alphabet[( (d>>12) & 0x3f )];
		dd[2] = (kSrc+1 < lenSrc) ? s_alphabet[( (d>> 6) & 0x3f )] : s_pad;
		dd[3] = (kSrc+2 < lenSrc) ? s_alphabet[( (d    ) & 0x3f )] : s_pad;

		pDest->overwrite(kDest,dd,4);
	}

	return true;
}

bool UT_Base64Decode(UT_ByteBuf * pDest, const UT_ByteBuf * pSrc)
{
	// decode the Base64 data in pSrc into pDest.
	// return false if error.

	UT_ASSERT(pDest && pSrc);

	pDest->truncate(0);

	UT_uint32 lenSrc = pSrc->getLength();
	if (lenSrc == 0)						// empty source buffer yields empty output buffer.
		return true;

	UT_ASSERT((lenSrc % 4) == 0);			// encoded data must consists of quads

	// compute the destination length and deal with pad bytes
	
	UT_uint32 lenDest = ((lenSrc + 3) / 4) * 3;
	const UT_Byte * p = pSrc->getPointer(0);
	if (p[lenSrc-1] == s_pad)
	{
		lenDest--;
		lenSrc--;
		if (p[lenSrc-1] == s_pad)
		{
			lenDest--;
			lenSrc--;
		}
	}

	// reserve space in the destination in advance.
	
	if (!pDest->ins(0,lenDest))
		return false;

	UT_uint32 kSrc, kDest;

	for (kSrc=0, kDest=0; (kSrc < lenSrc); kSrc+=4, kDest+=3)
	{
		// decode each group of 4 bytes in the input into 3 bytes in the output.

		bool bHave2 = ((kSrc+2) < lenSrc);
		bool bHave3 = ((kSrc+3) < lenSrc);
		
		UT_Byte s1 =            s_inverse(p[kSrc  ]);
		UT_Byte s2 =            s_inverse(p[kSrc+1]);
		UT_Byte s3 = (bHave2) ? s_inverse(p[kSrc+2]) : 0;
		UT_Byte s4 = (bHave3) ? s_inverse(p[kSrc+3]) : 0;

		UT_uint32 d = (s1<<18) | (s2<<12) | (s3<<6) | s4;

		UT_Byte dd[3];
		dd[0] = (UT_Byte)(           ( (d>>16) & 0xff )    );
		dd[1] = (UT_Byte)((bHave2) ? ( (d>> 8) & 0xff ) : 0);
		dd[2] = (UT_Byte)((bHave3) ? ( (d    ) & 0xff ) : 0);

		pDest->overwrite(kDest,dd, (1 + bHave2 + bHave3));
	}

	return true;
}

	
