/*
    GSK - a library to write servers
    Copyright (C) 1999-2000 Dave Benson

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA

    Contact:
        daveb@ffem.org <Dave Benson>
*/


#include "gskbase64.h"
#include <string.h>

static guint8 *to_base64 = (guint8 *) "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                      "abcdefghijklmnopqrstuvwxyz"
			              "0123456789"
			              "+/";
static guint  end_marker = '=';

static gboolean      inited_from_base64_table = 0;
static guint8        from_base64_table[256]   = { 0 };
static void init_from_base64_table()
{
  guint8 *at;
  guint8 val = 0;
  memset (from_base64_table, 255, 256);
  for (at = to_base64; *at != '\0'; at++)
    from_base64_table[*at] = val++;
}

static void gsk_base64_decode_internal   (char            *dst,
				          guint           *dst_len,
				          int              dst_len_max,
                                          const char      *src,
					  gsize            src_len)
{
  char *start_dst = dst;
  const char *end_src = src + src_len;
  int num_bits = 0;
  guint8 partial = 0;
  if (!inited_from_base64_table)
    init_from_base64_table ();

  while (dst_len_max > 0 && *src != '=' && src < end_src)
    {
      guint8 src_b64_encoded = *src++;
      guint8 src_6bits = from_base64_table[src_b64_encoded];
      if (src_6bits == 255)
	continue;
      if (num_bits == 0)
        {
	  /* Dst len isn't changed -- merely bits are shifted into 
	   * partial.
	   */
	  partial = src_6bits << 2;
	  num_bits = 6;
	}
      else
        {
	  /* The dst will have one byte shifted into it. */
	  partial |= (src_6bits >> (num_bits - 2));
	  *dst++ = partial;
	  dst_len_max--;
	  num_bits += (6 - 8);
	  if (num_bits == 0)
	    partial = 0;
	  else
	    partial = src_6bits << (8 - num_bits);
	}
    }
  *dst_len = dst - start_dst;
}

/**
 * gsk_base64_decode:
 * @dst: output area for binary data.
 * Should be GSK_BASE64_GET_MAX_DECODED_LEN(@src) long at least.
 * @dst_len: length of buffer allocated for @dst.
 * @src: base64 encoded data.
 * @src_len: length of the binary data, or -1 to assume that @src is
 * NUL-terminated.
 *
 * Decode a base64-encoded string into binary.
 * returns: number of bytes decoded.
 */
guint
gsk_base64_decode       (char            *dst,
			 guint            dst_len,
			 const char      *src,
			 gssize           src_len)
{
  guint rv;
  if (src_len < 0)
    src_len = strlen (src);
  gsk_base64_decode_internal (dst, &rv, dst_len, src, src_len);
  return rv;
}

/**
 * gsk_base64_decode_alloc:
 * @src: base64 encoded data, NUL terminated.
 *
 * Decode a base64-encoded string into binary.
 *
 * GSK_BASE64_GET_MAX_DECODED_LEN might not be the return value,
 * because it doesn't take a terminate '=' sign into account.
 * The return value should be exactly that if @src
 * is not = terminated, or GSK_BASE64_GET_MAX_DECODED_LEN()
 * is only called on the length which precedes the = sign.
 *
 * returns: the byte-array with the binary data.
 */
GByteArray *
gsk_base64_decode_alloc (const char      *src)
{
  const char *end;
  guint rv_size;
  int len;
  int decoded_len;
  GByteArray *rv;
  end = strchr (src, '=');
  if (end == NULL)
    len = strlen (src);
  else
    len = end - src;
  rv = g_byte_array_new ();
  decoded_len = (((len * 6) + 7) / 8);
  g_byte_array_set_size (rv, decoded_len);
  gsk_base64_decode_internal ((char*)rv->data, &rv_size, rv->len, src, len);
  if (rv->len != rv_size)
    {
      g_assert (rv->len > rv_size);
      g_byte_array_set_size (rv, rv_size);
    }
  return rv;
}

/**
 * gsk_base64_encode:
 * @dst: output base64 encoded string.  The result is NOT nul-terminated,
 * but is terminated with an = sign.  @dst should be exactly
 * GSK_BASE64_GET_ENCODED_LEN(@src_len) bytes long.
 * @src: input binary data.
 * @src_len: length of @src.
 *
 * base64 encodes binary data.
 */
void
gsk_base64_encode       (char            *dst,
			 const char      *src,
			 guint            src_len)
{
  /* constraint: num_bits is either 0, 2, 4 */
  int num_bits = 0;

  /* carry has only its most-significant num_bits set */
  guint8 carry = 0;

  while (src_len-- > 0)
    {
      guint cur = (guint8) *src++;

      int use;	/* # of bits to use from `cur' for the next byte of output */

      /* shovel as many bits as will fit into carry. */
      use = 6 - num_bits;

      carry |= (cur >> (8 - use));
      *dst++ = to_base64[carry];

      if (use == 2)
        {
	  cur &= 63;
	  *dst++ = to_base64[cur];
	  num_bits = 0;
	  carry = 0;
	}
      else
        {
	  num_bits = (8 - use);
	  cur <<= (6 - num_bits);
	  cur &= 63;
	  carry = cur;
	}
    }
  if (num_bits != 0)
    *dst++ = to_base64[carry];
  *dst = end_marker;
}


/**
 * gsk_base64_encode_alloc:
 * @src: data to base64 encode.
 * @src_len: length of binary data to encode, or -1 to take @src as a NUL-terminated string.
 *
 * base64 encodes binary data (that does not contain a NULL).
 *
 * returns: an newly allocated base64 encoded NUL-terminated ASCII string.
 */
char *
gsk_base64_encode_alloc (const char      *src,
			 gssize           src_len)
{
  unsigned raw_len = (src_len >= 0) ? (gsize)src_len : strlen (src);
  unsigned enc_len = GSK_BASE64_GET_ENCODED_LEN (raw_len);
  char *buf = g_malloc (enc_len + 1);
  gsk_base64_encode (buf, src, raw_len);
  buf[enc_len] = '\0';
  return buf;
}


syntax highlighted by Code2HTML, v. 0.9.1