/* base64.c -- Encode/decode integers in base64 format
 * Created: Mon Sep 23 16:55:12 1996 by faith@dict.org
 * Revised: Sat Mar 30 12:02:36 2002 by faith@dict.org
 * Copyright 1996, 2002 Rickard E. Faith (faith@dict.org)
 * 
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Library General Public License as published
 * by the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 * 
 * This library 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
 * Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * 
 * $Id: base64.c,v 1.5 2007/01/03 18:31:34 jcupitt Exp $
 *
 * \section{Base-64 Routines}
 *
 * \intro These routines use the 64-character subset of International
 * Alphabet IA5 discussed in RFC 1421 (printeable encoding) and RFC 1522
 * (base64 MIME).
 *

   Value Encoding  Value Encoding  Value Encoding  Value Encoding
       0 A            17 R            34 i            51 z
       1 B            18 S            35 j            52 0
       2 C            19 T            36 k            53 1
       3 D            20 U            37 l            54 2
       4 E            21 V            38 m            55 3
       5 F            22 W            39 n            56 4
       6 G            23 X            40 o            57 5
       7 H            24 Y            41 p            58 6
       8 I            25 Z            42 q            59 7
       9 J            26 a            43 r            60 8
      10 K            27 b            44 s            61 9
      11 L            28 c            45 t            62 +
      12 M            29 d            46 u            63 /
      13 N            30 e            47 v
      14 O            31 f            48 w         (pad) =
      15 P            32 g            49 x
      16 Q            33 h            50 y
 *
 */

/* 

	Hacked for VIPS ... does any length object (not just ints), formats
	base64 into 70 character lines, output to a malloc'd buffer.

	VIPS uses this to write BLOBs (like ICC profiles, for example) to the
	XML that follows an image.
 
Modified on: 
23/7/07 JC
	- oop, needed a slightly larger worst-case buffer in im__b64_encode()

 */

/*
#define DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

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

#include <vips/vips.h>

#include "base64.h"

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/

static unsigned char b64_list[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

#define XX 100

static unsigned char b64_index[256] = {
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
    52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
    XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
    15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
    XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
    41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};

/* Read (up to) 3 bytes from in. Be careful about byte ordering :-/ we need to
 * end up with in[2] in the bottom few bits.
 */
static int
read24( const unsigned char *in, size_t remaining )
{
	int bits;
	int i;

	bits = 0;
	for( i = 0; i < 3; i++ ) {
		bits <<= 8;
		if( remaining > 0 ) {
			bits |= in[i];
			remaining -= 1;
		}
	}

	return( bits );
}

/* Output (up to) 24 bits as four base64 chars. Pad with '=' characters.
 */
static void
encode24( char *p, int bits, size_t remaining )
{
	int i;

	for( i = 0; i < 4; i++ ) {
		if( remaining <= 0 )
			p[i] = '=';
		else {
			/* Take the top 6 bits of 24.
			 */
			p[i] = b64_list[(bits >> 18) & 63];
			bits <<= 6;
			remaining -= 6;
		}
	}
}

/* Output to a malloc'd buffer, NULL on error. Try to be simple and reliable,
 * rather than quick.
 */
char *
im__b64_encode( const unsigned char *data, size_t data_length )
{
	/* Worst case: 1.333 chars per byte, plus 10% for extra carriage 
	 * returns and stuff. And the \n\0 at the end.
	 */
	const size_t output_data_length = data_length * 44 / 30 + 2;

	char *buffer;
	char *p;
	size_t i;
	int cursor;

	if( data_length <= 0 ) {
		im_error( "im__b64_encode", _( "too little data" ) );
		return( NULL );
	}
	if( output_data_length > 1024 * 1024 ) {
		/* We shouldn't really be used for large amounts of data.
		 */
		im_error( "im__b64_encode", _( "too much data" ) );
		return( NULL );
	}
	if( !(buffer = im_malloc( NULL, output_data_length )) ) 
		return( NULL );

	p = buffer;
	*p++ = '\n';
	cursor = 0;

	for( i = 0; i < data_length; i += 3 ) {
		size_t remaining = data_length - i;
		int bits;

		bits = read24( data + i, remaining );
		encode24( p, bits, remaining * 8 );
		p += 4;
		cursor += 4;

		if( cursor >= 76 ) {
			*p++ = '\n';
			cursor = 0;
		}
	}
	if( cursor > 0 ) 
		*p++ = '\n';
	*p++ = '\0';

#ifdef DEBUG
{
	unsigned int total;

	/* Calculate a very simple checksum for debugging.
	 */
	for( total = 0, i = 0; i < data_length; i++ )
		total += data[i];

	printf( "im__b64_encode: length = %d, checksum 0x%x\n", 
		data_length, total & 0xffff );
}
#endif /*DEBUG*/

	return( buffer );
}

/* Decode base64 back to binary in a malloc'd buffer. NULL on error.
 */
unsigned char *
im__b64_decode( const char *buffer, size_t *data_length )
{
	const size_t buffer_length = strlen( buffer );

	/* Worst case.
	 */
	const size_t output_data_length = buffer_length * 3 / 4;

	unsigned char *data;
	unsigned char *p;
	unsigned int bits;
	int nbits;
	size_t i;

	if( output_data_length > 1024 * 1024 ) {
		/* We shouldn't really be used for large amounts of data.
		 */
		im_error( "im__b64_decode", _( "too much data" ) );
		return( NULL );
	}

	if( !(data = im_malloc( NULL, output_data_length )) )
		return( NULL );

	p = data;
	bits = 0;
	nbits = 0;

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

		if( (val = b64_index[(int) buffer[i]]) != XX ) {
			bits <<= 6;
			bits |= val;
			nbits += 6;

			if( nbits >= 8 ) {
				*p++ = (bits >> (nbits - 8)) & 0xff;
				nbits -= 8;
			}
		}
	}

	assert( p - data < output_data_length );

	if( data_length )
		*data_length = p - data;

#ifdef DEBUG
{
	unsigned int total;

	/* Calculate a very simple checksum for debugging.
	 */
	for( total = 0, i = 0; i < p - data; i++ )
		total += data[i];

	printf( "im__b64_decode: length = %d, checksum 0x%x\n", 
		p - data, total & 0xffff );
}
#endif /*DEBUG*/

	return( data );
}