/*
 * e4base64.c --
 *
 *	Implementation of BASE64 encoding/decoing (RFC 1341).
 *
 *	Authors: Jacob Levy and Jean-Claude Wippler.
 *		 jyl@best.com	jcw@equi4.com
 *
 *	Copyright: JYL Software, Inc., (c) 2000-2003.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE, EVEN IF
 * JYL SOFTWARE INC. IS MADE AWARE OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdlib.h>

/*
 * Ensure propert exporting of symbols on WIN32:
 */

/*
 * Export declarations:
 */

#ifndef e4_XMLDLL
    #if defined(_WIN32) && !defined(E4_STATIC)
	#ifdef E4_XMLDLL
            #define e4_XMLDLL      __declspec(dllexport)
        #else
            #define e4_XMLDLL      __declspec(dllimport)
        #endif
    #else
        #define e4_XMLDLL          
    #endif
#endif

/*
 * Implementation of base64 encode/decode exported by e4XML as a courtesy.
 */

typedef unsigned char byte;		/* Byte (8 bit quantity) type. */

/*
 * The base64 encoder and decoder functions:
 */

#define LINELEN		72		/* Encoded line length (max 76). */
#define FILLER		'='		/* Fill with this at end of input. */

static int initialized = 0;		/* Is encoding table initialized? */
static byte etable[256];		/* Encode table. */
static byte dtable[256];		/* Decode table. */

/*
 * Functions declared in this file:
 */

static int		base64_countchars(const char *base64str, int *slen);
static const char *	base64_getfour(const char *r, byte *a, byte *b);
static void		base64_initialize();

/*
 * base64_countchars --
 *
 * Count the number of non-blank characters in the base64 encoded input.
 */

static int
base64_countchars(const char *base64str, int *slen)
{
   const char *r;
   int i, j;

   for (i = 0, j = 0, r = base64str; *r != 0; r++, j++) {
	if ((*r != ' ') && (*r != '\n') && (*r != '\r') && (*r != '\t')) {
	    i++;
	}
   }

   if (slen != NULL) {
	*slen = j;
   }

   return i;
}

/*
 * base64_getfour --
 *
 * Get four characters out of the input string, skipping whitespace.
 */

static const char *
base64_getfour(const char *r, byte *a, byte *b)
{
    int i;
    byte c;

    for (i = 0; i < 4; i++) {
	c = (byte) *r++;
	if (c == '\0') {
	    return NULL;
	}
	if ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t')) {
	    i--;
	    continue;
	}
	a[i] = c;
	b[i] = (byte) dtable[c];
    }

    return r;
}

/*
 * base64_initialize --
 *
 * Ensure that the encode/decode tables are initialized.
 */

static void
base64_initialize()
{
    int i;

    if (!initialized) {
	initialized = 1;
	for (i = 0; i < 26; i++) {
	    etable[i] = 'A' + i;
	    etable[i + 26] = 'a' + i;
	}
	for (i = 0; i < 10; i++) {
	    etable[i + 52] = '0' + i;
	}
	etable[62] = '+';
	etable[63] = '/';

	for (i = 0; i < 255; i++) {
	    dtable[i] = 0x80;
	}
	for (i = 'A'; i <= 'Z'; i++) {
	    dtable[i] = 0 + (i - 'A');
	}
	for (i = 'a'; i <= 'z'; i++) {
	    dtable[i] = 26 + (i - 'a');
	}
	for (i = '0'; i <= '9'; i++) {
	    dtable[i] = 52 + (i - '0');
	}
	dtable['+'] = 62;
	dtable['/'] = 63;
	dtable['='] = 0;
    }
}

/*
 * base64_encode --
 *
 * Encode a string of 8-bit bytes into a base64 encoded character string.
 */

e4_XMLDLL char *
base64_encode(const byte *bytes, int len)
{
    int groups = (len / 3) + (((len % 3) == 0) ? 0 : 1);
					/* Figure out how many 3-byte
					 * groups there are in the input. */
    int charlen = groups * 4;		/* How many encoded characters? */
    int nlines = charlen / LINELEN;	/* How many lines in output? */
    int totallen = nlines + charlen + 1;/* One more for NULL at end. */
    int i, j, k, l, bytesingroup, charsonline;
    byte igroup[3];
    char ogroup[4];
    char *encoded;

    /*
     * Ensure the encode/decode table is initialized.
     */

    base64_initialize();

    /*
     * Allocate encoded data buffer.
     */

    encoded = (char *) malloc((unsigned) totallen);

    /*
     * Now encode every 24 bits of input into 32 bits of encoded data.
     */

    for (j = 0, k = 0, bytesingroup = 0, charsonline = 0, i = 0;
	 i < groups; 
	 i++, bytesingroup = 0) {

        /*
	 * Grab up to 3 input bytes.
	 */

	if (j < len) {
	    igroup[0] = bytes[j++];
	    bytesingroup++;
	} else {
	    igroup[0] = 0;
	}
	if (j < len) {
	    igroup[1] = bytes[j++];
	    bytesingroup++;
	} else {
	    igroup[1] = 0;
	}
	if (j < len) {
	    igroup[2] = bytes[j++];
	    bytesingroup++;
	} else {
	    igroup[2] = 0;
	}

	/*
	 * Encode the 3 input bytes into 4 output bytes.
	 */

	ogroup[0] = etable[igroup[0] >> 2];
	ogroup[1] = etable[((igroup[0] & 3) << 4) |
			   (igroup[1] >> 4)];
	ogroup[2] = etable[((igroup[1] & 0xF) << 2) |
			   (igroup[2] >> 6)];
	ogroup[3] = etable[igroup[2] & 0x3F];

	if (bytesingroup < 3) {
	    ogroup[3] = FILLER;
	    if (bytesingroup < 2) {
	 	ogroup[2] = FILLER;
	    }
	}

	/*
	 * Copy the new 4 output characters to the string of output.
	 */

	for (l = 0; l < 4; l++, k++) {
	    encoded[k] = ogroup[l];
	    charsonline++;
	    if (charsonline == LINELEN) {
		encoded[++k] = '\n';
		charsonline = 0;
	    }
	}
    }

    encoded[k] = '\0';

    return encoded;
}

/*
 * base64_decode --
 *
 * Decode a base64 character string into a string of 8-bit bytes.
 */

e4_XMLDLL byte *
base64_decode(const char *base64str, int *len)
{
    const char *r;
    byte *p;
    byte a[4], b[4], o[3];
    int slen, blen, j, k;
    int reallen, quads;
    byte *decoded;

    /*
     * Ensure the encode/decode tables are initialized.
     */

    base64_initialize();

    /*
     * Sanity check -- the number of real characters in the input string
     * must be a multiple of 4. If not, the input is not a base64 encoded
     * character string.
     */

    reallen = base64_countchars(base64str, &slen);
    if ((reallen % 4) != 0) {
	return NULL;
    }

    /*
     * The length of the encoded string is ((reallen / 4) * 3) - number of
     * filler characters at end.
     */

	quads = reallen / 4;
    blen = (quads * 3);
    if (base64str[slen - 1] == FILLER) {
	blen--;
    }
    if (base64str[slen - 2] == FILLER) {
	blen--;
    }

    /*
     * Allocate the buffer of 8-bit bytes.
     */

    decoded = (byte *) malloc((unsigned int) blen);
	if (decoded == NULL) {
	    return NULL;
	}

    /*
     * Loop over the input, decoding 4 characters at a time into 3 bytes.
     */

//    for (p = decoded, r = base64str; *r != '\0'; ) {
    for (p = decoded, r = base64str; quads--; ) {

        /*
	 * Get the next four input characters, skipping over white space.
	 */

        r = base64_getfour(r, a, b);
	if (r == NULL) {
		free(decoded);
	    return NULL;
	}

	/*
	 * Decode the next four input characters into three 8-bit bytes.
	 */

	o[0] = (b[0] << 2) | (b[1] >> 4);
	o[1] = (b[1] << 4) | (b[2] >> 2);
	o[2] = (b[2] << 6) | b[3];

	/*
	 * Determine how many valid 8-bit bytes were actually decoded.
	 */

	j = 3;
	if (a[3] == FILLER) {
	    j--;
	}
	if (a[2] == FILLER) {
	    j--;
	}

	/*
	 * Copy the decoded bytes into the output buffer.
	 */

	for (k = 0; j > 0; k++, j--) {
	    *p++ = o[k];
	}
    }

    /*
     * Finally return the decoded bytes and if desired, the length of
     * the decoded bytes.
     */

    if (len != NULL) {
	*len = blen;
    }

    return decoded;
}
