/*  base64.cpp
    Encoding of binary data according to RFC2045 base64 encoding

    13.08.2001: tk, initial implementation, based on base64.c
                    from the PHP 4.06 source code.

    Copyright (c)2000 by Thomas Kindler, thomas.kindler@gmx.de

    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. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files ----------
//
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "inet/base64.h"

static char base64_table[] =
	{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
	  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
	  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
	  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
	  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '\0'
	};

static char base64_pad = '=';

/** 
 * Encodes data with MIME base64.
 *
 * This encoding is designed to make binary data survive transport through
 * transport layers that are not 8-bit clean, such as mail bodies.
 *
 * Base64-encoded data takes about 33% more space than the original data.
 * \sa  base64_decode(), http://www.ietf.org/rfc/rfc2045.txt section 6.8.
 * 
 * \param   str         pointer to data to encode
 * \param   length      length of data to encode
 * \param   ret_length  pointer to an integer for storing the lenght
 *                      of the encoded data (may be NULL)
 * \return  pointer to encoded string, or 0 if out of memory.
 *          (needs to be free'd after use)
 *
 */
unsigned char *base64encode(const unsigned char *str, 
                            unsigned   length, 
                            unsigned  *ret_length) 
{
  const unsigned char *current = str;
  unsigned char *result;
  
  result = (unsigned char *)malloc((length+3 - length%3)*4/3 + 1);
  if (!result)
    return NULL;

  // keep going until we have less than 24 bits
  //
  unsigned i = 0;

  while (length > 2) { 
    result[i++] = base64_table[current[0] >> 2];
    result[i++] = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
    result[i++] = base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)];
    result[i++] = base64_table[current[2] & 0x3f];

    current += 3;
    length  -= 3;   // we just handle 3 octets of data
  }

  // now deal with the tail end of things
  //
  if (length != 0) {
    result[i++] = base64_table[current[0] >> 2];
    if (length > 1) {
      result[i++] = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
      result[i++] = base64_table[(current[1] & 0x0f) << 2];
      result[i++] = base64_pad;
    }
    else {
      result[i++] = base64_table[(current[0] & 0x03) << 4];
      result[i++] = base64_pad;
      result[i++] = base64_pad;
    }
  }
  if(ret_length)
    *ret_length = i;

  result[i]='\0';
  return result;
}


/**
 * Decodes data encoded with MIME base64.
 *
 * base64_decode() decodes encoded_data and returns the original data.
 * The returned data may be binary.
 *
 * \param str         pointer to base64 encoded string
 * \param length      maximum length for the decoded data
 * \param ret_length  pointer to an integer for storing the
 *                    actual length of the decoded data (may be NULL).
 * \return  pointer to decoded data, or 0 if out of memory.
 *          (needs to be free'd after use)
 *
 * \todo  This function contains a potential buffer overflow, if the
 *        given maximum length is too low. (It just doesn't check for
 *        overflow!). It better should calculate the length itself..
 */
unsigned char *base64decode( const unsigned char *str,
                             unsigned  length,
                             unsigned *ret_length)
{
  static int  reverse_table[256];
  static bool table_built = false;

  if (!table_built) {
    for(int ch = 0; ch < 256; ch++) {
      char *chp = strchr(base64_table, ch);
      if (chp) {
        reverse_table[ch] = (int)(chp - base64_table);
      }
      else
        reverse_table[ch] = -1;
    }
    table_built = true;
  }

  unsigned char *result;
  result = (unsigned char *)malloc(length + 1);
  if (result == NULL)
    return NULL;

  // run through the whole string, converting as we go
  //
	const unsigned char *current = str;
	int   ch, i = 0, j = 0, k;

	// run through the whole string, converting as we go
  while ((ch = *current++) != '\0') {
    if (ch == base64_pad)
      break;

    // When Base64 gets POSTed, all pluses are interpreted as spaces.
    // This line changes them back.  It's not exactly the Base64 spec,
    // but it is completely compatible with it (the spec says that
    // spaces are invalid).  This will also save many people considerable
    // headache.  - Turadg Aleahmad <turadg@wise.berkeley.edu>
    //
    if (ch == ' ') 
      ch = '+'; 

    ch = reverse_table[ch];
    if (ch < 0) 
      continue;

    switch(i % 4) {
      case 0:
        result[j] = ch << 2;
        break;
      case 1:
        result[j++] |= ch >> 4;
        result[j] = (ch & 0x0f) << 4;
        break;
      case 2:
        result[j++] |= ch >>2;
        result[j] = (ch & 0x03) << 6;
        break;
      case 3:
        result[j++] |= ch;
        break;
    }
    i++;
  }
  
  // mop things up if we ended on a boundary
  //
  k = j;
  if (ch == base64_pad) {
    switch(i % 4) {
      case 0:
      case 1:
        free(result);
        return NULL;
      case 2:
        k++;
      case 3:
        result[k++] = 0;
    }
  }
  assert(j<length); 
  
  if (ret_length)
    *ret_length = j;

  result[k] = '\0';
  return result;
}

