/* sml3_base64.c: Base64-Funktionen */

/* Copyright 2012-2017 Kurt Nienhaus
 *
 * This file is part of libsammel3.
 * libsammel3 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.
 * libsammel3 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 libsammel3.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "config.h"
#include "sml3_fehler.h"
#include "sml3_util.h"
#include "sml3_base64.h"

void SML3_base64_encode(char **, size_t *, const char *, size_t, int, struct SML3_base64_block *);
void SML3_base64_decode(char **, size_t *, const char *, size_t, struct SML3_base64_block *);
void SML3_html64_encode(char **, size_t *, const char *, size_t, struct SML3_base64_block *);
void SML3_html64_decode(char **, size_t *, const char *, size_t, struct SML3_base64_block *);
void SML3_file64_encode(const char *, char *, size_t);
void SML3_file64_decode(const char *, char *, size_t);

static void encode64(char **, size_t *, const char *, size_t, int, struct SML3_base64_block *, const char *);
static void decode64(char **, size_t *, const char *, size_t, struct SML3_base64_block *, const char *);

static const char z_base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char z_file64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";


/* SML3_base64_encode [thread-sicher]:
 * vercodet nach Base64
 * 1.Arg: fuer Rueckgabe vercodete Daten (wird alloziert), enthaelt Ende-0
 * 2.Arg: fuer Rueckgabe Anzahl Bytes im 1.Arg (ohne Ende-0), oder NULL
 * 3.Arg: zu vercodende Daten
 * 4.Arg: Anzahl Bytes im 3.Arg
 * 5.Arg: Anzahl Zeichen, nach denen Newline eingefuegt werden soll
 *        oder  0 = nie
 *        oder -1 = einmalig am Schluss
 * 6.Arg: Adresse auf Hilfsstruct fuer blockweise Vercodung
 *          (muss anfangs mit SML3_BASE64_BLOCK_INITIALIZER oder SML3_BASE64_BLOCK_REINITIALIZE() initialisiert sein)
 *        oder NULL = Einmalvercodung
 * Bei blockweiser Vercodung muss zum Schluss das Hilfsstruct geleert werden
 * durch Aufruf SML3_base64_encode() und 3.Arg=NULL und 4.Arg=0
 */
void
SML3_base64_encode(char **bcode, size_t *scode, const char *buncode, size_t suncode, int nlz, struct SML3_base64_block *hblock)
{
  encode64(bcode, scode, buncode, suncode, nlz, hblock, z_base64);
} /* Ende SML3_base64_encode */


/* SML3_base64_decode [thread-sicher]:
 * decodet von Base64
 * 1.Arg: fuer Rueckgabe decodete Daten (wird alloziert), enthaelt Ende-0
 * 2.Arg: fuer Rueckgabe Anzahl Bytes im 1.Arg (ohne Ende-0), oder NULL
 * 3.Arg: zu decodende Daten
 * 4.Arg: Anzahl Bytes im 3.Arg
 * 5.Arg: Adresse auf Hilfsstruct fuer blockweise Decodung
 *          (muss anfangs mit SML3_BASE64_BLOCK_INITIALIZER oder SML3_BASE64_BLOCK_REINITIALIZE() initialisiert sein)
 *        oder NULL = Einmaldecodung
 * Bei blockweiser Decodung muss zum Schluss das Hilfsstruct geleert werden
 * durch Aufruf SML3_base64_decode() und 3.Arg=NULL und 4.Arg=0
 */
void
SML3_base64_decode(char **buncode, size_t *suncode, const char *bcode, size_t scode, struct SML3_base64_block *hblock)
{
  decode64(buncode, suncode, bcode, scode, hblock, z_base64);
} /* Ende SML3_base64_decode */


/* SML3_html64_encode [thread-sicher]:
 * vercodet nach modifiziertem html-faehigen Base64
 * 1.Arg: fuer Rueckgabe vercodete Daten (wird alloziert), enthaelt Ende-0
 * 2.Arg: fuer Rueckgabe Anzahl Bytes im 1.Arg (ohne Ende-0), oder NULL
 * 3.Arg: zu vercodende Daten
 * 4.Arg: Anzahl Bytes im 3.Arg
 * 5.Arg: Adresse auf Hilfsstruct fuer blockweise Vercodung
 *          (muss anfangs mit SML3_BASE64_BLOCK_INITIALIZER oder SML3_BASE64_BLOCK_REINITIALIZE() initialisiert sein)
 *        oder NULL = Einmalvercodung
 * Bei blockweiser Vercodung muss zum Schluss das Hilfsstruct geleert werden
 * durch Aufruf SML3_html64_encode() und 3.Arg=NULL und 4.Arg=0
 */
void
SML3_html64_encode(char **bcode, size_t *scode, const char *buncode, size_t suncode, struct SML3_base64_block *hblock)
{
  encode64(bcode, scode, buncode, suncode, 0, hblock, z_file64);
} /* Ende SML3_html64_encode */


/* SML3_html64_decode [thread-sicher]:
 * decodet von modifiziertem html-faehigen Base64
 * 1.Arg: fuer Rueckgabe decodete Daten (wird alloziert), enthaelt Ende-0
 * 2.Arg: fuer Rueckgabe Anzahl Bytes im 1.Arg (ohne Ende-0), oder NULL
 * 3.Arg: zu decodende Daten
 * 4.Arg: Anzahl Bytes im 3.Arg
 * 5.Arg: Adresse auf Hilfsstruct fuer blockweise Decodung
 *          (muss anfangs mit SML3_BASE64_BLOCK_INITIALIZER oder SML3_BASE64_BLOCK_REINITIALIZE() initialisiert sein)
 *        oder NULL = Einmaldecodung
 * Bei blockweiser Decodung muss zum Schluss das Hilfsstruct geleert werden
 * durch Aufruf SML3_html64_decode() und 3.Arg=NULL und 4.Arg=0
 */
void
SML3_html64_decode(char **buncode, size_t *suncode, const char *bcode, size_t scode, struct SML3_base64_block *hblock)
{
  decode64(buncode, suncode, bcode, scode, hblock, z_file64);
} /* Ende SML3_html64_decode */


/* SML3_file64_encode [thread-sicher]:
 * vercodet base64-aehnlich, geeignet fuer Dateinamen
 * 1.Arg: Dateiname
 * 2.Arg: fuer Rueckgabe vercodeter Dateiname
 * 3.Arg: sizeof(2.Arg)
 */
void
SML3_file64_encode(const char *file_in, char *file_out, size_t fout_size)
{
  char *bcode;
  size_t scode, suncode;

  if (file_in == NULL) { file_in = ""; }
  suncode = strlen(file_in);
  if (suncode > 0 && file_in[suncode - 1] == '\n') { suncode--; }
  if (suncode > 0 && file_in[suncode - 1] == '\r') { suncode--; }
  encode64(&bcode, &scode, file_in, suncode, 0, NULL, z_file64);

  if (file_out != NULL && fout_size > 0) {
    if (scode >= fout_size) { scode = fout_size - 1; }
    if (scode > 0) { memmove(file_out, bcode, scode); }
    file_out[scode] = '\0';
  }
  free(bcode);
} /* Ende SML3_file64_encode */


/* SML3_file64_decode [thread-sicher]:
 * decodet base64-aehnlich, geeignet fuer Dateinamen
 * 1.Arg: Dateiname
 * 2.Arg: fuer Rueckgabe decodeter Dateiname
 * 3.Arg: sizeof(2.Arg)
 */
void
SML3_file64_decode(const char *file_in, char *file_out, size_t fout_size)
{
  char *buncode;
  size_t suncode, scode;

  if (file_in == NULL) { file_in = ""; }
  scode = strlen(file_in);
  if (scode > 0 && file_in[scode - 1] == '\n') { scode--; }
  if (scode > 0 && file_in[scode - 1] == '\r') { scode--; }
  decode64(&buncode, &suncode, file_in, scode, NULL, z_file64);

  if (file_out != NULL && fout_size > 0) {
    if (suncode >= fout_size) { suncode = fout_size - 1; }
    if (suncode > 0) { memmove(file_out, buncode, suncode); }
    file_out[suncode] = '\0';
  }
  free(buncode);
} /* Ende SML3_file64_decode */


/* vercodet */
static void
encode64(char **bcode, size_t *scode, const char *buncode, size_t suncode, int nlz, struct SML3_base64_block *hblock, const char *z64)
{
  char zbuf[4];
  size_t zlen, zanz, opos, zahl;
  int *nlzp, nlzi, i1, i2, bit;

  if (bcode == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  *bcode = NULL;
  if (scode != NULL) { *scode = 0; }
  if (buncode == NULL) { suncode = 0; }
  if (suncode == 0) { buncode = NULL; }

  /* Rueckgabepuffer gross genug allozieren */
  opos = 0;
  if (hblock != NULL) { opos += hblock->srest; }
  opos += suncode;
  if (opos == 0) {
    *bcode = SML3_calloc(2, 1);
    if (nlz != 0 && hblock != NULL && (nlz < 0 || hblock->u.enc.nlz > 0)) { (*bcode)[0] = '\n'; }  /* Ende-Newline */
    return;
  }
  opos = opos * 4 / 3 + 3;  /* 3 Inputzeichen werden 4 Outputzeichen */
  if (nlz > 0) { opos += (opos / nlz) + 2; }  /* Newlines dazu */
  if (nlz != 0 && (hblock == NULL || buncode == NULL)) { opos++; }  /* Ende-Newline */
  opos++;  /* Ende-0 */
  *bcode = SML3_malloc(opos * sizeof(char));
  opos = 0;

  /* Newlines */
  nlzi = 0;
  if (hblock != NULL) { nlzp = &hblock->u.enc.nlz; } else { nlzp = &nlzi; }

  /* jeweils 3 Inputzeichen in 4 Outputzeichen konvertieren */
  for (zlen = 0; ; zlen = 0) {
    if (hblock != NULL && hblock->srest > 0) {
      zlen = hblock->srest;
      memmove(zbuf, hblock->u.enc.brest, zlen);
      hblock->srest = 0;
    }
    if (suncode > 0) {
      if (zlen + suncode < 3) { zanz = suncode; } else { zanz = 3 - zlen; }
      memmove(zbuf + zlen, buncode, zanz);
      buncode += zanz;
      suncode -= zanz;
      zlen += zanz;
    }

    if (buncode != NULL && hblock != NULL && zlen < 3) {  /* keine 3 Inputzeichen mehr bei blockweiser Vercodung */
      if (zlen > 0) { memmove(hblock->u.enc.brest, zbuf, zlen); }
      hblock->srest = (int)zlen;
      break;
    }
    if (zlen == 0) { break; }

    zahl = 0;
    for (i1 = 0; i1 < (int)zlen; i1++) {
      zahl += (size_t)((1 << ((3 - (i1 + 1)) * 8)) * (int)(unsigned char)zbuf[i1]);
    }

    for (i1 = 0; i1 <= (int)zlen; i1++) {
      bit = 0;
      for (i2 = 1; i2 <= 6; i2++) {
        zahl <<= 1;
        bit <<= 1;
        if (zahl & 16777216) { bit++; }  /* Bit 24 */
      }
      (*bcode)[opos++] = z64[bit];
      if (nlz > 0 && ++(*nlzp) == nlz) { (*bcode)[opos++] = '\n'; *nlzp = 0; }
    }

    if ((int)zlen < 3) {  /* waren keine 3 Inputzeichen mehr */
      if (z64 == z_base64) {
        while ((int)zlen < 3) {  /* Rest zu 4 Outputzeichen mit '=' auffuellen */
          (*bcode)[opos++] = '=';
          if (nlz > 0 && ++(*nlzp) == nlz) { (*bcode)[opos++] = '\n'; *nlzp = 0; }
          zlen++;
        }
      }
      break;
    }
  }

  if (opos > 0 && nlz != 0 && (hblock == NULL || buncode == NULL)) {  /* Ende-Newline */
    if (nlz < 0 || (nlz > 0 && (*bcode)[opos - 1] != '\n')) { (*bcode)[opos++] = '\n'; }
  }
  (*bcode)[opos] = '\0';
  if (scode != NULL) { *scode = opos; }
} /* Ende encode64 */


/* decodet */
static void
decode64(char **buncode, size_t *suncode, const char *bcode, size_t scode, struct SML3_base64_block *hblock, const char *z64)
{
  const size_t mblock = 1024;
  char *pt0;
  size_t zlen, zanz, opos, osize, zahl;
  size_t zbuf[4];
  int i1;

  if (buncode == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  *buncode = NULL;
  if (suncode != NULL) { *suncode = 0; }
  if (bcode == NULL) { scode = 0; }
  if (scode == 0) { bcode = NULL; }

  /* Rueckgabepuffer wegen Schrottzeichen blockweise allozieren */
  opos = 0;
  if (hblock != NULL) { opos += hblock->srest; }
  opos += scode;
  if (opos == 0) {
    *buncode = SML3_calloc(1, 1);
    return;
  }
  osize = opos * 3 / 4 + 4;
  if (osize > mblock) { osize = mblock; }
  *buncode = SML3_malloc(osize * sizeof(char));
  opos = 0;

  /* jeweils 4 Inputzeichen in 3 Outputzeichen konvertieren */
  for (zlen = 0; ; zlen = 0) {
    if (hblock != NULL && hblock->srest > 0) {
      zlen = hblock->srest;
      memmove(zbuf, hblock->u.dec.brest, zlen * sizeof(size_t));
      hblock->srest = 0;
    }
    while (scode > 0) {
      if (*bcode != '\0' && (pt0 = strchr(z64, (int)*bcode)) != NULL) {
        zbuf[zlen++] = (size_t)((unsigned char *)pt0 - (const unsigned char *)z64);
      }
      bcode++;
      scode--;
      if (zlen == 4) { break; }
    }

    if (bcode != NULL && hblock != NULL && zlen < 4) {  /* keine 4 Inputzeichen mehr bei blockweiser Decodung */
      if (zlen > 0) { memmove(hblock->u.dec.brest, zbuf, zlen * sizeof(size_t)); }
      hblock->srest = (int)zlen;
      break;
    }
    if (zlen < 2) { break; }  /* mindestens 2 Inputzeichen muessen gelesen worden sein */

    zahl = 0;
    for (i1 = 0; i1 < (int)zlen; i1++) {
      zahl += (zbuf[i1] << ((3 - i1) * 6));
    }

    zanz = ((size_t)255 << 16);
    for (i1 = 0; i1 < (int)zlen - 1; i1++) {
      (*buncode)[opos++] = (char)((zahl & zanz) >> ((2 - i1) * 8));
      if (opos == osize) {
        if (scode > mblock) { osize += mblock; } else { osize += scode + 4; }
        *buncode = SML3_realloc(*buncode, osize * sizeof(char));
      }
      zanz >>= 8;
    }

    if ((int)zlen < 4) { break; }  /* waren keine 4 Inputzeichen mehr */
  }

  if (opos == osize) {
    osize++;
    *buncode = SML3_realloc(*buncode, osize * sizeof(char));
  }
  (*buncode)[opos] = '\0';
  if (suncode != NULL) { *suncode = opos; }
} /* Ende decode64 */
