Skip to content

File base64.c

File List > base > base64.c

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 *  base64.c -  Base64 encoding and decoding
 *
 */

#ident "$Id$"

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>
#include <stddef.h>
#include <assert.h>

#include "porting.h"
#include "error_code.h"
#include "memory_alloc.h"
#include "base64.h"
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"

#define  MAX_BASE64_LINE_LENGTH      76
#define  CH_INVALID                  -1
#define  CH_SPACE                    -2

/* core structure for decoding */
typedef struct base64_chunk BASE64_CHUNK;
struct base64_chunk
{
  char base64_bytes[4];     /* position in base64 table for encoded bytes */
  int padding_len;      /* number of padding */
};

/*
 *   Helper table for encoding
 */
const char *const base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/";

/*
 *  Helper table for decoding.
 *  -2 means  space character
 *  -1 means  invalid character
 *   positive values mean valid character
 */
const char from_base64_table[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -1,
  -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  /* !"#$%&'()*+,-./ */
  -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
  /* 0123456789:;<=>? */
  52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
  /* @ABCDEFGHIJKLMNO */
  -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
  /* PQRSTUVWXYZ[\]^_ */
  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
  /* `abcdefghijklmno */
  -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
  /* pqrstuvwxyz{|}~ */
  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};

static bool char_is_invalid (unsigned char ch);
static bool char_is_padding (unsigned char ch);
static int get_base64_encode_len (int src_len);
static int base64_encode_local (const unsigned char *src, int src_len, unsigned char *dest);
static int base64_remove_space (const unsigned char *src, int src_len, unsigned char *dest, int *dst_len);
static int base64_partition_into_chunk (const unsigned char *src, int src_len, int *chunk_num, int *dst_len,
                    BASE64_CHUNK *** pppchunk);
static int base64_decode_chunk (unsigned char *dest, int chunk_num, BASE64_CHUNK ** ppchunk);
static void free_base64_chunk (BASE64_CHUNK ** ppchunk, int chunk_num);

/*
 * char_is_invalid () -
 *   return: true if input char is invalid character, otherwise false
 *   ch(in): input character
 */
static bool
char_is_invalid (unsigned char ch)
{
  return (ch != '=' && from_base64_table[ch] == CH_INVALID);
}

/*
 * char_is_padding () -
 *   return: true if input char is padding('='), otherwise false
 *   ch(in): input character
 */
static bool
char_is_padding (unsigned char ch)
{
  return (ch == '=');
}

/*
 * find_base64 () -
 *   return: offset in base64 table for input character
 *   ch(in): input character
 */
static int
find_base64 (unsigned char ch)
{
  int offset;
  const char *pos = NULL;

  pos = strchr (base64_map, ch);
  assert (pos != NULL);

  offset = CAST_STRLEN (pos - base64_map);
  return offset;
}

/*
 * base64_partition_into_chunk () - Partition base64 encoded string into
 *                                  multiple chunks. Partition stops when
 *                                  meeting the first chunk ending in
 *                                  padding(s).
 *
 *   return:  int(NO_ERROR if successful, otherwise other base64 error code)
 *   src(in): source string(base64 encoded)
 *   src_len(in):   source string length
 *   chunk_num_out(in/out): actual number of chunks calculated
 *   dst_len_out(in/out):   actual length of buffer to store decoded bytes
 *   pppchunk(in/out):      array pointer of chunks to store encoded bytes
 */
static int
base64_partition_into_chunk (const unsigned char *src, int src_len, int *chunk_num_out, int *dst_len_out,
                 BASE64_CHUNK *** pppchunk)
{
  int err = NO_ERROR;
  int i, j, chunk_count, chunk_num, dst_len;
  unsigned char d1, d2, d3, d4;
  bool tail_chunk_flag;
  BASE64_CHUNK *chk = NULL;
  BASE64_CHUNK **ppchunk = NULL;

  assert (src != NULL && src_len > 0 && chunk_num_out != NULL && dst_len_out != NULL && pppchunk != NULL);

  *chunk_num_out = 0;
  *dst_len_out = 0;
  *pppchunk = NULL;

  /* require 4-byte alignment for chunk */
  if ((src_len & 0x3) != 0)
    {
      err = BASE64_INVALID_INPUT;
      goto end;
    }

  /* chunk array pointer to accommodate maximum chunk_num base64 chunks */
  chunk_num = src_len / 4;
  ppchunk = (BASE64_CHUNK **) db_private_alloc (NULL, chunk_num * sizeof (BASE64_CHUNK *));

  if (ppchunk == NULL)
    {
      err = ER_OUT_OF_VIRTUAL_MEMORY;
      goto end;
    }

  memset (ppchunk, 0, chunk_num * sizeof (BASE64_CHUNK *));

  tail_chunk_flag = false;
  i = chunk_count = dst_len = 0;
  while (chunk_count < chunk_num)
    {
      d1 = src[i];
      d2 = src[i + 1];
      d3 = src[i + 2];
      d4 = src[i + 3];

      /* check all invalid conditions */
      if (char_is_invalid (d1) || char_is_invalid (d2) || char_is_invalid (d3) || char_is_invalid (d4)
      || char_is_padding (d1) || char_is_padding (d2))
    {
      err = BASE64_INVALID_INPUT;
      goto end;
    }

      if (char_is_padding (d3) && !char_is_padding (d4))
    {
      err = BASE64_INVALID_INPUT;
      goto end;
    }

      chk = (BASE64_CHUNK *) db_private_alloc (NULL, sizeof (BASE64_CHUNK));
      if (chk == NULL)
    {
      err = ER_OUT_OF_VIRTUAL_MEMORY;
      goto end;
    }

      memset (chk, 0, sizeof (BASE64_CHUNK));

      chk->padding_len = char_is_padding (d4) ? 1 : 0;
      chk->padding_len += char_is_padding (d3) ? 1 : 0;

      for (j = 0; j <= (3 - chk->padding_len); j++)
    {
      chk->base64_bytes[j] = (char) find_base64 (src[i + j]);
    }

      ppchunk[chunk_count] = chk;

      if (char_is_padding (d4) == true)
    {
      /* middle chunk that has padding is invalid */
      if ((chunk_count + 1) != chunk_num)
        {
          err = BASE64_INVALID_INPUT;
          goto end;
        }
      else          /* tail chunk that has padding */
        {
          /* how many decoded bytes to add in this chunk, e.g, from_base64(YWE=)='aa',from_base64(YW==)='a' */
          dst_len += char_is_padding (d3) ? 1 : 2;
          tail_chunk_flag = true;
        }
    }

      chunk_count++;

      if (tail_chunk_flag == true)
    {
      break;
    }

      dst_len += 3;
      i += 4;
    }

end:

  if (err == NO_ERROR)
    {
      *chunk_num_out = chunk_count;
      *dst_len_out = dst_len;
      *pppchunk = ppchunk;
    }
  else
    {
      /* free resource if error */
      if (ppchunk != NULL)
    {
      free_base64_chunk (ppchunk, chunk_count);
    }

      *chunk_num_out = 0;
      *dst_len_out = 0;
      *pppchunk = NULL;
    }

  return err;
}

/*
 * base64_remove_space () - remove space characters to facilitate base64
 *                           chunk partition
 *   return: NO_ERROR
 *   src(in): input string
 *   size(in):length of input string
 *   dest(in/out): buffer to store the string that has no space characters
 *   dst_len(in/out): length of dest
 *
 *   Note: dest is allocated in this function but freed in caller. dst_len may
 *         differ from size in case of space characters.
 */
static int
base64_remove_space (const unsigned char *src, int size, unsigned char *dest, int *dst_len)
{
  int i;
  unsigned char *q;

  assert (src != NULL && dest != NULL && dst_len != NULL);

  *dst_len = 0;
  q = dest;

  i = 0;
  while (i < size)
    {
      /* search for the next char that is non-space character */
      while (from_base64_table[src[i]] == CH_SPACE && i < size)
    {
      i++;
    }

      if (i >= size)
    {
      break;
    }

      /* be careful of memory bounds! */
      *q++ = src[i++];
      (*dst_len)++;
    }

  return NO_ERROR;
}

/*
 *  base64_decode_chunk () - convert encoded bytes stored in chunks to
 *                           plain-text bytes
 *   return:        NO_ERROR
 *   dest(in/out):  buffer to store decoded bytes
 *   chunk_num(in): number of chunks
 *   ppchunk(in): buffer to store chunks
 *
 *  Note:  each chunk has four encoded bytes and can be converted to [1,2,3]
 *         decoded bytes depending on padding.
 */
static int
base64_decode_chunk (unsigned char *dest, int chunk_num, BASE64_CHUNK ** ppchunk)
{
  int i, d, copy_num;
  char decode_bytes[3];
  BASE64_CHUNK *chk;

  assert (dest != NULL && ppchunk != NULL);

  i = 0;
  d = 0;
  while (i < chunk_num)
    {
      chk = ppchunk[i];
      d += (chk->base64_bytes[0]) << 18;
      d += (chk->base64_bytes[1]) << 12;
      d += (chk->base64_bytes[2]) << 6;
      d += (chk->base64_bytes[3]) << 0;

      decode_bytes[0] = (d >> 16) & 0xff;
      decode_bytes[1] = (d >> 8) & 0xff;
      decode_bytes[2] = (d >> 0) & 0xff;

      /* only allow for 0,1,2 */
      assert (chk->padding_len < 3);
      copy_num = 3 - chk->padding_len;

      memcpy (dest, decode_bytes, copy_num);

      dest += copy_num;
      d = 0;
      i++;
    }

  return NO_ERROR;
}

/*
 *  base64_decode() -
 *   return:        int(NO_ERROR if successful,otherwise failure error code)
 *   src(in):       source plain-text string
 *   src_len(in):   length of source string
 *   dest(in/out):  buffer to store decoded bytes, allocated in this func
 *   dest_len(in/out): required length to store decoded buffer
 *
 *  Note:  there are some error handlings to handle invalid characters.
 *         some buffers are allocated in  this function or its callees,
 *         and freed in the end.
 *
 */
int
base64_decode (const unsigned char *src, int src_len, unsigned char **dest, int *dest_len)
{
  int error_status = NO_ERROR;
  int len_no_space, real_dest_len, chunk_num = 0;
  unsigned char *str_no_space = NULL;
  unsigned char *real_dest = NULL;
  BASE64_CHUNK **ppchunk = NULL;

  assert (src != NULL && dest != NULL && dest_len != NULL);

  *dest = NULL;
  *dest_len = 0;

  len_no_space = 0;

  /* assume there are no space characters in source string */
  str_no_space = (unsigned char *) db_private_alloc (NULL, src_len + 1);
  if (str_no_space == NULL)
    {
      error_status = ER_OUT_OF_VIRTUAL_MEMORY;
      goto buf_clean;
    }

  /* remove space characters */
  base64_remove_space (src, src_len, str_no_space, &len_no_space);

  /* ' ' has spaces. after space removal, it is empty string */
  if (len_no_space == 0)
    {
      error_status = BASE64_EMPTY_INPUT;
      goto buf_clean;
    }

  /* Partition encoded bytes with no space characters into multiple chunks for decoding, stop when there is a middle
   * chunk ending in padding(s), which is considered as invalid input */
  error_status = base64_partition_into_chunk (str_no_space, len_no_space, &chunk_num, &real_dest_len, &ppchunk);

  if (error_status != NO_ERROR)
    {
      goto buf_clean;
    }

  real_dest = (unsigned char *) db_private_alloc (NULL, real_dest_len + 1);
  if (real_dest == NULL)
    {
      error_status = ER_OUT_OF_VIRTUAL_MEMORY;
      goto buf_clean;
    }

  real_dest[real_dest_len] = '\0';

  assert (chunk_num > 0);
  base64_decode_chunk (real_dest, chunk_num, ppchunk);

  *dest = real_dest;
  *dest_len = real_dest_len;

  error_status = NO_ERROR;

buf_clean:

  if (str_no_space != NULL)
    {
      db_private_free_and_init (NULL, str_no_space);
    }

  if (error_status != NO_ERROR && real_dest != NULL)
    {
      db_private_free_and_init (NULL, real_dest);
    }

  if (ppchunk != NULL)
    {
      free_base64_chunk (ppchunk, chunk_num);
    }

  return error_status;
}

/*
 * get_base64_encode_len () -
 *   return:  the string length with base64 encoding
 *   src_len(in):   source string length
 */
static int
get_base64_encode_len (int src_len)
{
  int nbytes, backspace_bytes_need, total_bytes;

  assert (src_len >= 0);

  nbytes = (src_len + 2) / 3 * 4;

  /* need to account for backspaces in the encoded string */
  backspace_bytes_need = nbytes / MAX_BASE64_LINE_LENGTH;
  if (nbytes % MAX_BASE64_LINE_LENGTH == 0)
    {
      --backspace_bytes_need;
    }

  total_bytes = nbytes + backspace_bytes_need;

  return total_bytes;
}

/*
 *  base64_encode () -
 *   return:  NO_ERROR(NO_ERROR if successful,otherwise failure error code)
 *   src(in):     source plain-text string
 *   src_len(in): length of source string
 *   dest(in/out):  base64 encoded string
 *   dest_len(in/out): required length for encoded string
 *
 */
int
base64_encode (const unsigned char *src, int src_len, unsigned char **dest, int *dest_len)
{
  int encode_len;
  int error_status;
  unsigned char *dest_p = NULL;

  assert (src != NULL && src_len >= 0 && dest != NULL && dest_len != NULL);

  *dest = NULL;
  *dest_len = 0;

  encode_len = get_base64_encode_len (src_len);

  dest_p = (unsigned char *) db_private_alloc (NULL, encode_len + 1);
  if (dest_p == NULL)
    {
      error_status = ER_OUT_OF_VIRTUAL_MEMORY;
      goto end;
    }

  error_status = base64_encode_local (src, src_len, dest_p);

  if (error_status == NO_ERROR)
    {
      dest_p[encode_len] = '\0';

      *dest = dest_p;
      *dest_len = encode_len;
    }
  else
    {
      if (dest_p != NULL)
    {
      db_private_free_and_init (NULL, dest_p);
    }
    }

end:

  return error_status;
}

/*
 *  base64_encode_local () - convert a plain-text string with base64,
 *                            invoked by base64_encode
 *   return:        NO_ERROR
 *   src(in):       source plain-text string
 *   src_len(in):   length of source string
 *   dest(in):      base64 encoded string
 *
 *  Note: it's the caller's responsibility to claim and reclaim(if needed)
 *        memory space for dest
 */
static int
base64_encode_local (const unsigned char *src, int src_len, unsigned char *dest)
{
  const unsigned char *p;
  unsigned int d;
  int i, encoded_len, fill, line_break_count;

  assert (src != NULL && src_len >= 0 && dest != NULL);

  encoded_len = 0;
  line_break_count = 0;
  p = src;

  i = 0;
  while (i < src_len)
    {
      /* require maximum char length per line to be 76 */
      if (line_break_count == MAX_BASE64_LINE_LENGTH)
    {
      dest[encoded_len++] = '\n';
      line_break_count = 0;
    }

      /* move forward the source string every three bytes and translate it into four 6-bit bytes */
      d = (p[i++] << 16);   /* the most significant(3rd) bytes */
      fill = 2;         /* assuming 2 paddings needed */

      if (src_len >= (i + 1))
    {
      d += p[i++] << 8; /* the second byte, one padding needed */
      fill = 1;
    }

      if (src_len >= (i + 1))   /* least significant byte, no padding needed */
    {
      d += p[i++] << 0;
      fill = 0;
    }

      /* convert 24bit stream into four 6-bit bytes */
      dest[encoded_len++] = base64_map[(d >> 18) & 0x3f];
      dest[encoded_len++] = base64_map[(d >> 12) & 0x3f];
      dest[encoded_len++] = (fill > 1) ? '=' : base64_map[(d >> 6) & 0x3f];
      dest[encoded_len++] = (fill > 0) ? '=' : base64_map[(d >> 0) & 0x3f];

      line_break_count += 4;
    }

  return NO_ERROR;
}

/*
 *  free_base64_chunk () - free allocated base64_chunk
 *   return:
 *   pchunk(in):       source plain-text string
 *
 */
static void
free_base64_chunk (BASE64_CHUNK ** ppchunk, int chunk_num)
{
  int i = 0;

  if (ppchunk != NULL)
    {
      for (i = 0; i < chunk_num; ++i)
    {
      if (ppchunk[i] != NULL)
        {
          db_private_free (NULL, ppchunk[i]);
        }
    }

      db_private_free (NULL, ppchunk);
    }

  return;
}