/*
 * Copyright (C) Tildeslash Ltd. All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3.
 *
 * 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 Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU Affero General Public License in all respects
 * for all of the code used other than OpenSSL.  
 */

#include "config.h"

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "monit.h"
#include "base64.h"


/**
 *  Implementation of base64 encoding/decoding. 
 *
 *  @file
 */


/* ----------------------------------------------------------------- Private */


/**
 * Base64 encode one byte
 */
static char encode(unsigned char u) {

        if(u < 26)  return 'A'+u;
        if(u < 52)  return 'a'+(u-26);
        if(u < 62)  return '0'+(u-52);
        if(u == 62) return '+';

        return '/';

}


/**
 * Decode a base64 character
 */
static unsigned char decode(char c) {

        if(c >= 'A' && c <= 'Z') return(c - 'A');
        if(c >= 'a' && c <= 'z') return(c - 'a' + 26);
        if(c >= '0' && c <= '9') return(c - '0' + 52);
        if(c == '+')             return 62;

        return 63;

}


/**
 * Return TRUE if 'c' is a valid base64 character, otherwise FALSE
 */
static int is_base64(char c) {

        if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
           (c >= '0' && c <= '9') || (c == '+')             ||
           (c == '/')             || (c == '=')) {

                return TRUE;

        }

        return FALSE;

}


/* ------------------------------------------------------------------ Public */


/**
 * Base64 encode and return size data in 'src'. The caller must free the
 * returned string.
 * @param size The size of the data in src
 * @param src The data to be base64 encode
 * @return encoded string otherwise NULL
 */
char *encode_base64(size_t size, unsigned char *src) {

  int i;
  char *out, *p;

  if(!src)
    return NULL;

  if(!size)
    size= strlen((char *)src);

  out= CALLOC(sizeof(char), size*4/3+4);

  p= out;

  for(i=0; i<size; i+=3) {

    unsigned char b1=0, b2=0, b3=0, b4=0, b5=0, b6=0, b7=0;

    b1 = src[i];

    if(i+1<size)
      b2 = src[i+1];

    if(i+2<size)
      b3 = src[i+2];

    b4= b1>>2;
    b5= ((b1&0x3)<<4)|(b2>>4);
    b6= ((b2&0xf)<<2)|(b3>>6);
    b7= b3&0x3f;

    *p++= encode(b4);
    *p++= encode(b5);

    if(i+1<size) {
      *p++= encode(b6);
    } else {
      *p++= '=';
    }

    if(i+2<size) {
      *p++= encode(b7);
    } else {
      *p++= '=';
    }

  }

  return out;

}


/**
 * Decode the base64 encoded string 'src' into the memory pointed to by
 * 'dest'. The dest buffer is <b>not</b> NUL terminated.
 * @param dest Pointer to memory for holding the decoded string.
 * Must be large enough to recieve the decoded string.
 * @param src A base64 encoded string.
 * @return TRUE (the length of the decoded string) if decode
 * succeeded otherwise FALSE.
 */
size_t decode_base64(unsigned char *dest, const char *src) {

  if(src && *src) {

    unsigned char *p= dest;
    size_t k, l = strlen(src)+1;
    unsigned char *buf= CALLOC(1, l);


    /* Ignore non base64 chars as per the POSIX standard */
    for(k=0, l=0; src[k]; k++) {

      if(is_base64(src[k])) {
	
	buf[l++]= src[k];
	
      }

    } 

    for(k=0; k<l; k+=4) {

      char c1='A', c2='A', c3='A', c4='A';
      unsigned char b1=0, b2=0, b3=0, b4=0;

      c1= buf[k];

      if(k+1<l) {
	
	c2= buf[k+1];
	
      }

      if(k+2<l) {
	
	c3= buf[k+2];
	
      }

      if(k+3<l) {
	
	c4= buf[k+3];
	
      }

      b1= decode(c1);
      b2= decode(c2);
      b3= decode(c3);
      b4= decode(c4);

      *p++=((b1<<2)|(b2>>4) );

      if(c3 != '=') {
	
	*p++=(((b2&0xf)<<4)|(b3>>2) );
	
      }

      if(c4 != '=') {
	
	*p++=(((b3&0x3)<<6)|b4 );
	
      }

    }

    FREE(buf);

    return(p-dest);

  }

  return FALSE;

}
