/* $Id: base64.c,v 1.5 2001/07/29 18:00:17 richdawe Exp $ */

/*
 *  base64.c - Base64 encoding/decoding routines for zippo
 *  Copyright (C) 2000, 2001 by Richard Dawe
 *      
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Base64 encoding is described in RFC 2045 - see section 6.8, "Base64
 * Content-Transfer-Encoding". RFC 2045 is entitled "Multipurpose Internet
 * Mail Extensions (MIME) Part One: Format of Internet Message Bodies". See
 * http://www.rfc-editor.org/ for more details.
 */

#include "common.h"

#include <limits.h>
#include <string.h>
#include <errno.h>

#include "base64.h"

/* Byte type */
typedef unsigned char b64_byte;

/* Type to divide unencoded/decoded byte stream into 24-bit grouping. */
typedef b64_byte b64_dgroup[3];

/* Type to divide encoded byte stream into 32-bit grouping. */
typedef b64_byte b64_egroup[4];

/* Bit-manipulation macros */
#undef MASK_T6
#undef MASK_B2
#undef MASK_T4
#undef MASK_B4
#undef MASK_T2
#undef MASK_B6
#undef MASK_B8
	
#define MASK_T6(x)	((x) & 0xfc)
#define MASK_B2(x)   	((x) & 0x03)
#define MASK_T4(x)	((x) & 0xf0)
#define MASK_B4(x)	((x) & 0x0f)
#define MASK_T2(x)	((x) & 0xc0)
#define MASK_B6(x)   	((x) & 0x3f)
#define MASK_B7(x)   	((x) & 0x7f)
#define MASK_B8(x)   	((x) & 0xff)

/* Macros to get four 6-bit bytes from 3 8-bits bytes (x, y, z). */
#undef GET6_B1
#undef GET6_B2
#undef GET6_B3
#undef GET6_B4

#define GET6_B1(x)	(MASK_T6(x) >> 2)
#define GET6_B2(x, y)	((MASK_B2(x) << 4) | (MASK_T4(y) >> 4))
#define GET6_B3(y, z)	((MASK_B4(y) << 2) | (MASK_T2(z) >> 6))
#define GET6_B4(z)	MASK_B6((z))

/* Macros to get three 8-bit bytes from 4 6-bit bytes (w, x, y, z). */
#undef GET8_B1
#undef GET8_B2
#undef GET8_B3

/* Top two bits of x zero => can use MASK_T4 to get two bits. */
/* Top two bits of y zero => can use MASK_T6 to get four bits. */
#define GET8_B1(w, x)	MASK_B8( ((w) << 2) | (MASK_T4(x) >> 4) )
#define GET8_B2(x, y)	MASK_B8( (MASK_B4(x) << 4) | (MASK_T6(y) >> 2) )
#define GET8_B3(y, z)	MASK_B8( (MASK_B2(y) << 6) | (z) )

/* Base64 encoding table - allow space for a nul terminator. */
static unsigned char base64_encoding_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"		/* 0-25   */
"abcdefghijklmnopqrstuvwxyz"		/* 26-51  */
"0123456789"				/* 52-61  */
"+/";					/* 62, 63 */

/* Encoding macro */
#undef ENCODE6
#define ENCODE6(x) (base64_encoding_table[(int) MASK_B6(x)])

/* Base64 decoding table - allow space for a nul terminator. 255 => error. */
static unsigned char base64_decoding_table[129] =
{ 255, 255, 255, 255, 255, 255, 255, 255,	/* 0-7                    */
  255, 255, 255, 255, 255, 255, 255, 255,	/* 8-15                   */
  255, 255, 255, 255, 255, 255, 255, 255,	/* 16-23                  */
  255, 255, 255, 255, 255, 255, 255, 255,	/* 24-31                  */
  255, 255, 255, 255, 255, 255, 255, 255,     	/* 32-39                  */
  255, 255, 255, 62,  255, 255, 255, 63,     	/* 40-42, 43 (+), 44-46,
						 * 47 (/)                 */
  52,  53,  54,  55,  56,  57,  58,  59,	/* 48-55 (0-7)            */
  60,  61,  255, 255, 255, 0,   255, 255,	/* 56,57 (8,9), 58-60,
						 * 61 (=), 62, 63         */
  255, 0,   1,   2,   3,   4,   5,   6,		/* 64, 65-71 (A-G)        */
  7,   8,   9,   10,  11,  12,  13,  14,	/* 72-79 (H-O)            */
  15,  16,  17,  18,  19,  20,  21,  22,	/* 80-87 (P-W)            */
  23,  24,  25,  255, 255, 255, 255, 255,	/* 88-90 (X-Z), 91-95     */
  255, 26,  27,  28,  29,  30,  31,  32,	/* 96, 97-103 (a-g)       */
  33,  34,  35,  36,  37,  38,  39,  40,	/* 104-111 (h-o)          */
  41,  42,  43,  44,  45,  46,  47,  48,	/* 112-119 (p-w)          */
  49,  50,  51,  255, 255, 255, 255, 255	/* 120-122 (x-z), 123-127 */
};

/* Decoding macro */
#undef DECODE8
#define DECODE8(x) (base64_decoding_table[(int) MASK_B7(x)])

/* Base64 padding character */
static unsigned char base64_pad = '=';

/* ---------------------------
 * - base64_get_encoded_size -
 * --------------------------- */

ssize_t
base64_get_encoded_size (size_t insize)
{
  ssize_t ret = 0;

  /* The size should be a multiple of 3 bytes (24 bits). These 3 bytes
   * are then converted into 4 bytes. */
  ret = insize;
  while ((ret % 3) != 0) { ret++; }
  ret = (ret / 3) * 4;
	
  return(ret);
}

/* -----------------
 * - base64_encode -
 * ----------------- */

ssize_t
base64_encode (void *in, size_t insize, void *out, size_t *outsize)
{
  b64_byte *b_in  = (b64_byte *) in;
  b64_byte *b_out = (b64_byte *) out;
  b64_byte *b_dg  = NULL;
  b64_byte *b_eg  = NULL;	
  ssize_t   ret   = 0;
  size_t    s     = 0;
  size_t    max_s = 0;

  /* Check the buffers & output length are writeable. */
  if ((in == NULL) || (out == NULL) || (outsize == NULL)) {
    errno = EFAULT;
    return(-1);
  }

  /* Out size > in size? */
  if (*outsize < insize) {
    errno = EINVAL;
    return(-1);
  }

  /* Encode all the complete 24-bit groups. */
  max_s = insize / 3;
	
  for (b_dg = b_in, b_eg = b_out, s = 0; s < max_s; s++) {
    b_eg[0] = ENCODE6( GET6_B1( b_dg[0] ) );
    b_eg[1] = ENCODE6( GET6_B2( b_dg[0], b_dg[1] ) );
    b_eg[2] = ENCODE6( GET6_B3( b_dg[1], b_dg[2] ) );
    b_eg[3] = ENCODE6( GET6_B4( b_dg[2] ) );

    b_dg += sizeof(b64_dgroup);
    b_eg += sizeof(b64_egroup);
  }

  /* Now encode any left-overs. */
  s = insize % 3;

  switch(s) {
    /* Only 8-bits left => 16-bits of padding in group
     * -> two padding chars. */
  case 1:
    b_eg[0] = ENCODE6( GET6_B1(b_dg[0]) );
    b_eg[1] = ENCODE6( GET6_B2(b_dg[0], 0) );
    b_eg[2] = b_eg[3] = base64_pad;
    break;

    /* 16-bits left -> 8 bits of padding in group
     * -> one padding char. */
  case 2:
    b_eg[0] = ENCODE6( GET6_B1( b_dg[0]) );
    b_eg[1] = ENCODE6( GET6_B2( b_dg[0], b_dg[1]) );
    b_eg[2] = ENCODE6( GET6_B3( b_dg[1], 0) );
    b_eg[3] = base64_pad;
    break;

    /* Even multiple of 24-bits from input. */
  case 0:
  default:		
    break;
  }

  if (ret == 0) *outsize = base64_get_encoded_size(insize);
  return(ret);
}

/* ---------------------------
 * - base64_get_decoded_size -
 * --------------------------- */

ssize_t
base64_get_decoded_size (void *in, size_t insize)
{
  b64_byte *b_in    = (b64_byte *) in;
  b64_byte *p       = NULL;
  ssize_t   padding = 0;
  ssize_t   ret     = 0;

  /* Base64 encoding pads to a length that is a multiple of 24-bits
   * (groups of 4x6-bits). */
  if ((insize % 4) != 0) {
    errno = EINVAL;
    return(-1);
  }

  /* Range check, to prevent overflow in ret. */
  if (insize > SSIZE_MAX) {
    errno = EFBIG;
    return(-1);
  }

  ret = insize * 3 / 4;
	
  /* How much padding is there at the end? */
  for (padding = 0, p = b_in + insize - 1; *p == base64_pad; p--) {
    padding++;
  }

  /* Each padding character corresponds to one (unused) byte of
   * output. */
  ret -= padding;
	
  return(ret);
}

/* -----------------
 * - base64_decode -
 * ----------------- */

ssize_t
base64_decode (void *in, size_t insize, void *out, size_t *outsize)
{
  b64_byte *b_in      = (b64_byte *) in;
  b64_byte *b_out     = (b64_byte *) out;
  b64_byte *b_dg      = NULL;
  b64_byte *b_eg      = NULL;
  b64_byte  b_quad[4] = { 0, 0, 0, 0 };
  ssize_t   ret       = 0;
  size_t    s         = 0;
  size_t    max_s     = 0;
  size_t    c         = 0;

  /* Check the buffers are writeable. */
  if ((in == NULL) || (out == NULL)) {
    errno = EFAULT;
    return(-1);
  }

  /* Is the input buffer size a multiple of four? */
  if ((insize % 4) != 0) {
    errno = EINVAL;
    return(-1);
  }

  /* Is the output buffer big enough? */
  max_s = base64_get_decoded_size(in, insize);
  if (max_s > *outsize) {
    errno = EINVAL;
    return(-1);
  }
	
  /* Decode each 32-bit group -> 24-bit group (maybe including
   * padding). */
  b_eg = b_in;
  b_dg = b_out;
  s = 0;

  while (s < max_s) {
    /* Decode ASCII chars to 6-bit binary. The padding character
     * is 0 in the table, so this handles padding bytes. */
    memcpy(b_quad, b_eg, sizeof(b_quad));

    c = sizeof(b64_dgroup);
    if (b_quad[2] == base64_pad) c--;
    if (b_quad[3] == base64_pad) c--;
		
    b_quad[0] = DECODE8(b_quad[0]);
    b_quad[1] = DECODE8(b_quad[1]);
    b_quad[2] = DECODE8(b_quad[2]);
    b_quad[3] = DECODE8(b_quad[3]);

    /* Convert quadruple to three bytes. */
    switch(c) {
    case 3:
      /* 3 data bytes */
      b_dg[2] = GET8_B3(b_quad[2], b_quad[3]);
    case 2:
      /* 2 data bytes */
      b_dg[1] = GET8_B2(b_quad[1], b_quad[2]);
    case 1:
      /* 1 data byte */
      b_dg[0] = GET8_B1(b_quad[0], b_quad[1]);
    default:
      break;
    }
				
    /* Move on to next group of bytes. */
    b_eg += sizeof(b64_egroup);
    b_dg += sizeof(b64_dgroup);
    s    += c;
  }

  if (ret == 0) *outsize = s;
  return(ret);
}
