/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 * 
 * \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()

12/5/09
	- fix signed/unsigned warning

25/3/11
	- move to vips_ namespace

31/5/15
	- oops siged/unsigned mess-up meant we were not padding correctly

20/3/19
	- larger output allocate
	- better max size check

 */

/*
#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 <vips/internal.h>

#ifdef HAVE_BASE64_ENCODE

/* glib 2.12 adds these.
 */
char *
vips__b64_encode( const unsigned char *data, size_t data_length )
{
	return( g_base64_encode( data, data_length ) );
}

unsigned char *
vips__b64_decode( const char *buffer, size_t *data_length )
{
	return( g_base64_decode( buffer, data_length ) );
}

#else /*!HAVE_BASE64_ENCODE*/

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, int remaining )
{
	int bits;
	int i;

	bits = 0;
	for( i = 0; i < 3; i++ ) {
		bits = VIPS_LSHIFT_INT( 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, int 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 = VIPS_LSHIFT_INT( bits, 6 );
			remaining -= 6;
		}
	}
}

/* Output to a malloc'd buffer, NULL on error. Try to be simple and reliable,
 * rather than quick.
 */
char *
vips__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, plus the final \n\0.
	 */
	const size_t output_data_length = data_length * 44 / 30 + 10;

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

	if( data_length > 10 * 1024 * 1024 ) {
		/* We shouldn't really be used for large amounts of data, plus
		 * we are using int offsets.
		 *
		 * A large ICC profile can be 1MB, so allow 10MB.
		 */
		vips_error( "vips__b64_encode", "%s", _( "too much data" ) );
		return( NULL );
	}
	if( !(buffer = vips_malloc( NULL, output_data_length )) ) 
		return( NULL );

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

	for( i = 0; i < data_length; i += 3 ) {
		int 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';

	g_assert( (size_t) (p - buffer) < output_data_length );

#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( "vips__b64_encode: length = %zu, 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 *
vips__b64_decode( const char *buffer, size_t *data_length )
{
	const size_t buffer_length = strlen( buffer );

	/* Worst case. Add one, since we don't want to return NULL for an empty
	 * input string, it would look like an error return.
	 */
	const size_t output_data_length = 1 + buffer_length * 3 / 4;

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

	/* A large ICC profile can be a couple of MB, so 10 should be plenty.
	 */
	if( output_data_length > 10 * 1024 * 1024 ) {
		/* We shouldn't really be used for large amounts of data, plus
		 * we are using an int for offset.
		 */
		vips_error( "vips__b64_decode", "%s", _( "too much data" ) );
		return( NULL );
	}

	if( !(data = vips_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 = VIPS_LSHIFT_INT( bits, 6 );
			bits |= val;
			nbits += 6;

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

	g_assert( (size_t) (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( "vips__b64_decode: length = %d, checksum 0x%x\n", 
		p - data, total & 0xffff );
}
#endif /*DEBUG*/

	return( data );
}

#endif /*HAVE_BASE64_ENCODE*/
