/*
 * Utility functions for base64 encoding / decoding according to
 * http://www.faqs.org/rfcs/rfc1113.html section 4.3.2.4
 *
 * Limits:
 *
 * - '*' (not encrypted) is unimplemented
 * - Encoding line width limitation not implemented, the base64 encoded string
 *   will not be split up into multiple lines
 * - Decoding of multi line input is not implemented
 *
 * Written by Poul-Henning Kamp <phk@phk.freebsd.dk>
 *        and Nils Goroll <slink@uplex.de>
 *
 * This file is in the public domain.
 */

#include "config.h"

#include <sys/types.h>
#include <stdarg.h>
#include "varnishapi.h"

static const char b64[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static char i64[256];
static int init_done = 0;

void
base64_init(void)
{
	int i;
	const char *p;

	/* can we race? Yes, we can. Do we care? No! */
	if (init_done)
		return;

	for (i = 0; i < 256; i++)
		i64[i] = -1;
	for (p = b64, i = 0; *p; p++, i++)
		i64[(int)*p] = (char)i;
	i64['='] = 0;

	init_done = 1;
}

/*
 * Args: destination buffer, buffer length
 *       source data
 *
 * 	 source data must be a null-terminated string
 *
 * return length of decoded data in d or -1 upon error
 *
 * to facilitate decoding of strings, a final null byte will be appended, but
 * not returned as the length
 *
 * d must be large enough to store the null byte
 *
 * implementation note: When encountering the N/A char '=', we finish decoding
 * the block, but limit the number of bytes being output
 */

int
base64_decode(char *d, unsigned dlen, const char *s)
{
	unsigned u, v, w, l;
	int i;

	u = 0;
	l = 0;

	while (*s) {
		w = 3;
		for (v = 0; v < 4; v++) {
			if (!*s)
				break;
			i = i64[(int)*s];
			if (i < 0)
				return (-1);
			u <<= 6;
			u |= i;
			if ((*s++ == '=') && (w == 3)) {
				if (v == 3)
					w = 2;
				else if (v == 2)
					w = 1;
			}
		}
		for (v = 0; v < w; v++) {
			if (l >= dlen - 1)
				return (-1);
			*d = (u >> 16) & 0xff;
			u <<= 8;
			l++;
			d++;
		}
	}
	*d = '\0';
	return (l);
}

/*
 * "left" and "right" part of the respective input bytes, thinking big endian,
 * shifted to the right base64 output position
 */

#define b1_l(s)	((s[0] & 0xfc) >> 2)
#define b1_r(s) ((s[0] & 0x03) << 4)

#define b2_l(s) ((s[1] & 0xf0) >> 4)
#define b2_r(s) ((s[1] & 0x0f) << 2)

#define b3_l(s) ((s[2] & 0xc0) >> 6)
#define b3_r(s) ((s[2] & 0x3f))

/*
 * write one 4-byte block into e (must be large enough),
 * encoding b = (1..3) bytes from s
 *
 * performance note: Should be compiler-optimizable when being fed a constant b
 */

static inline void
_b64block(char *e, const char *s, char b)
{
	switch (b) {
	case 3:
		e[3] = b64[ b3_r(s) ];
		e[2] = b64[ b3_l(s) | b2_r(s) ];
	case 2:
		e[1] = b64[ b2_l(s) | b1_r(s) ];
	case 1:
		e[0] = b64[ b1_l(s) ];
	}
	switch (b) {
	case 1:
		e[1] = b64[ b1_r(s) ];
		e[2] = '=';
		e[3] = '=';
		break;
	case 2:
		e[2] = b64[ b2_r(s) ];
		e[3] = '=';
	}
}

/*
 * Args: destination buffer, buffer length
 *       source data, source length
 *
 * return length of encoded data in d or -1 upon error
 */

int
base64_encode(char *e, unsigned elen, const char *s, unsigned n)
{
	char b;
	int  l;

	l = 0;

	while (n) {
		if (elen < (l + 5))
			return (-1);

		b = (n < 3) ? n : 3;

		_b64block(e, s, b);

		l += 4;
		e += 4;
		s += 3;
		n -= b;
	}
	*e='\0';
	return (++l);
}

/*
 * same as base64_encode, but encode strings passed as varargs as if they were
 * one concatenated string.
 *
 * term is the terminator for the string list, this will usually be
 * vrt_magic_string_end when called from VRT, VMOD etc. When a non-NULL
 * terminator is passed, null arguments will be skipped over.
 *
 * if NULL is passed as a terminator, varargs processing will stop on a NULL
 * pointer
 */

#if 0
#define b64assert(x)	assert(x)
#else
#define b64assert(x)	((void)(x))
#endif

int
base64_encode_string(char *e, unsigned elen, const void *term, const char *s, va_list ap)
{
	const char	*p;
	char		b;
	int		l;
	/* temp space to carry over three bytes from one vararg to the next */
	char		carry[2];
	int		carry_l;

	l = carry_l = 0;

	if (elen == 0)
		return -1;

	p = s;
	while (p != term) {
		if (p == NULL)
			goto nextp;

		/*
		 * fill up the array carrying the last bytes from the previous
		 * string until this string ends or carry is full
		 */
		if (carry_l) {
			while (*p && (carry_l < 3))
				carry[carry_l++] = *p++;

			if (carry_l < 3)
				goto nextp;

			b64assert(carry_l == 3);

			if (elen < (l + 5))
				return -1;

			_b64block(e, carry, 3);
			carry_l = 0;
			l += 4;
			e += 4;
		}

		b64assert(carry_l == 0);

		while (*p) {
			if (elen < (l + 5))
				return -1;
			
			if (*(p+1))
				if (*(p+2))
					b = 3;
				else
					b = 2;
			else
				b = 1;

			if (b == 3) {
				_b64block(e, p, 3);

				l += 4;
				e += 4;
				p += 3;
			} else {
				b64assert (b > 0);

				if (carry_l == 2) {
					carry[carry_l] = *p;
					p++;
					b--;
					_b64block(e, carry, 3);
					l += 4;
					e += 4;
					carry_l = 0;
				}

				b64assert((carry_l + b) <= 3);

				switch (b) {
				case 2:
					carry[carry_l + 1] = *(p+1);
				case 1:
					carry[carry_l] = *p;
				}
				carry_l += b;
				p += b;
			}
		}
	  nextp:
		p = va_arg(ap, const char *);
	}

	/* Anything left ? */

	if (carry_l) {
		if (elen < (l + 5))
			return -1;

		_b64block(e, carry, carry_l);
		l += 4;
		e += 4;
	}

	*e='\0';
	return (++l);
}

#ifdef TEST_DRIVER

/* gcc -DTEST_DRIVER -I ../.. -I ../../include ../libvarnish/assert.c base64.c */

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

#define MAX_PRINTABLE 5
#define MAX_TEST 11

const void * const magic_string_end = &magic_string_end;

const char *test[MAX_TEST+1] = {
	"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz"
	"IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg"
	"dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu"
	"dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo"
	"ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=", /* 0 */
	"", /* 1 */
	/* all following tests encoded using GNU coreutils base64 */
	"YQ==", /* 2: "a" */
	"YWI=", /* 3: "ab" */
	"YWJj", /* 4: "abc" */
	"UGhrIGFuZCBWYXJuaXNoIHNob3VsZCByZWFsbHkgYmUgYXdhcmRlZCBhIHByaXplIGZvciBwcm90"
	"ZWN0aW5nIG91ciBlbnZpcm9ubWVudCBieQpzaWduaWZpY2FudGx5IGxvd2VyaW5nIHRoZSBDTzIg"
	"Zm9vdHByaW50IC8gZW5lcmd5IGNvbnN1bXB0aW9uIGJ5IHRvZGF5J3MgaW50ZXJuZXQKaW5kdXN0"
	"cnkuIFRoZSBvbmx5IHF1ZXN0aW9uIGlzOiBEb2VzIHN1Y2ggYSBwcml6ZSBleGlzdD8K", /* 5 */
	/* 
	 * 6: just some random data from:
	 * export LC_CTYPE=C
	 * dd if=/dev/random bs=256 count=1 | tr '\000' '0' | base64
	 */
	"qZzhtC90adeYmJSbUUIXti2e6ob7Ru+QeVl9UiiqM9xtsRFh6gnWIFGDCksSmhZ74FkIPj34UawD"
	"U7Db/5cWWMclJf+B55jmttozMvDW9IumypB/XjMr1W9e9gyiOOoRc1snjIGhyEdiRwvsOl2V0mX0"
	"7e7StefOtImNz80gIWtGxq+e5KKW5GiJmVamla2zGNj2Sdvo1LV1CGOh3PhwGwJwmHOn9R1/zZ5x"
	"XWosmz90Kp5RLDbsdDmc/jmJvGzZkE66p0554Xl4rFdu7jSWEqoQKGYboCa8YDAGzKhB0C+xth/J"
	"F9T42OINv9VXkIjB0Vs+0geY3GifRqbjFPmk",
	/*
	 * 7: random data with nulls
	 */
	"KN3KvtjkNP70IBlGqYzRMbUCAJdpux0VXTVGNGVP3iGbTr4fYJ/S78iKQNPVf1TWWhJPLT0ENZVI"
	"OP4UYWP9Laja4hsPn3d9iunTzG8fDovDXwRlmyTL5xzI1OC34MNwA0nVxoSsHCDHvIBT1uz3ZEaM"
	"k4N6A8Uwifk5IlLsoeWOU83ao1+jqNXkhrLcuZOaccIr5GfI7KU+igswmH22+ncaIIuAhc8fIzSv"
	"n01grCHeB5H3O/+Yt8ycQIoUmf+HkbpIm4v//AWIZVivgzeI2dMoJ0a8xWV+MsgKg+2NwnknIVLB"
	"dfukgA32FjiuJ0lWZAUVsakmk0QChtTLQpkDqg==",
	/* various lengths of zero buffers */
	"AA==",		/*  8 */
	"AAA=",		/*  9 */
	"AAAA",		/* 10 */
	"AAAAAA=="	/* 11 */
};

int
base64_encode_s(char *e, unsigned elen, const char *s, ...)
{
	va_list ap;
	int     r;

	va_start(ap, s);
	r = base64_encode_string(e, elen, magic_string_end, s, ap);
	va_end(ap);
	return (r);
}

void
print_varargs(const char *s, ...)
{
	va_list ap;
	const char *p;

	va_start(ap, s);
	p = s;
	while (p != magic_string_end) {
		if (p)
			printf(">%s< ", p);
		else
			printf("(null) ");
		p = va_arg(ap, const char *);
	}
	printf("\n");
	va_end(ap);
}

int
main(int argc, char **argv)
{
	int i, j, k, l;
	char buf1[BUFSIZ];
	char buf2[BUFSIZ];

	int l_dec, l_dec_est, l_dec_str;
	int l_enc, l_enc_est, l_enc_str;

	/* split for varargish encode */
#define CHUNK_LEN 9
#define NCHUNK 8
	char chunk[NCHUNK][CHUNK_LEN];
	const char *cp;


	(void)argc;
	(void)argv;

	base64_init();

	
	for (i = 0; i <= MAX_TEST; i++) {
		printf("\nTest %d:\n-------\n", i);

		/* decode */

		if ((l_dec = base64_decode(buf1, sizeof(buf1), test[i])) < 0) {
			fprintf(stderr, "decode(test[%d]) failed\n", i);
			return (1);
		}
		l_dec_est = base64_decode_l(strlen(test[i]));
			
		if (i <= MAX_PRINTABLE) {
			printf("%s\n", buf1);
			l_dec_str = strlen(buf1) + 1;
			printf("decode length estimated: %d, actual %d, strlen+1 %d\n",
			    l_dec_est, l_dec, l_dec_str);
		} else {
			printf("decode length estimated: %d, actual %d\n",
			    l_dec_est, l_dec);
		}

		if (l_dec_est < l_dec) {
			fprintf(stderr, "decode length estimate to small for test %d\n", i);
		}


		/* encode */

		if ((l_enc = base64_encode(buf2, sizeof(buf2), buf1, l_dec)) < 0) {
			fprintf(stderr, "encode(test[%d]) failed\n", i);
			return (1);
		}

		l_enc_est = base64_encode_l(l_dec);
		if (i <= MAX_PRINTABLE) {
			l_enc_str = strlen(buf2) + 1;
			printf("encode length estimated: %d, actual %d, strlen+1 %d\n",
			    l_enc_est, l_enc, l_enc_str);
		} else {
			printf("encode length estimated: %d, actual %d\n",
			    l_enc_est, l_enc);
		}

		if (l_enc_est < l_enc) {
			fprintf(stderr, "encode length estimate to small for test %d\n", i);
		}
			

		printf("%s\n", test[i]);
		printf("%s\n", buf2);

		if (strcmp(test[i], buf2) != 0) {
			fprintf(stderr, "encode(decode(test[%d])) != test[%d]\n", i, i);
			return (1);
		}

		if ((i > MAX_PRINTABLE) || (l_dec < (NCHUNK * CHUNK_LEN / 2)))
			continue;
		
		/* varargisch encode: split input into chunks */
		for (j = 0; j < CHUNK_LEN; j++) {
			cp = buf1;

			/* permutate lengths of chunks */
			for (k = 0; k < NCHUNK && *cp; k++) {
				l = strnlen(cp, (j + k + 1) % CHUNK_LEN);
				strncpy(chunk[k], cp, l);
				cp += l;
				chunk[k][l] = '\0';
			}

			l_enc = base64_encode_s(buf2, sizeof(buf2),
			    chunk[0], NULL, "",
			    chunk[1], chunk[2], chunk[3],
			    "", "", "", "", NULL, "",
			    chunk[4], chunk[5], chunk[6],
			    chunk[7], cp, magic_string_end);
			if (l_enc < 0) {
				fprintf(stderr, "encode(test[%d]) varargs %d"
				    "failed\n", i, j);
				return (1);
			}

			if (strcmp(test[i], buf2) != 0) {
				fprintf(stderr, "encode(decode(test[%d])) "
				    "varargs %d != test[%d]:\n", i, j, i);

				print_varargs(
				    chunk[0], NULL, "",
				    chunk[1], chunk[2], chunk[3],
				    "", "", "", "", NULL, "",
				    chunk[4], chunk[5], chunk[6],
				    chunk[7], cp, magic_string_end);
				printf("%s\n", buf2);

				return (1);
			}
		}
	}

	return (0);
}

#undef MAX_PRINTABLE
#undef MAX_TEST
#undef CHUNK_LEN
#undef NCHUNK
#endif
