/*
 * This file is part of uudeview, the simple and friendly multi-part multi-
 * file uudecoder  program  (c)  1994 by Frank Pilhofer. The author may be
 * contacted by his email address,          fp@informatik.uni-frankfurt.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.
 *
 * 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.
 */



/*
 * These functions do the uudecoding. It is converted from a freeware
 * program named "uunconc". It didn't contain the name of the author.
 * If you are the author or know him, contact me.
 * The original program only covered uuencoding. Base64 and XX scheme
 * covered by myself.
 **/

#include <stdio.h>
#include <ctype.h>

#ifndef NO_POSIX
#include <string.h>
#include <malloc.h>
#endif

#ifndef _DOS
#ifndef NO_POSIX
#include <unistd.h>
#endif
#endif

#include "uudeview.h"
#include "uuerror.h"
#include "uuutil.h"
#include "uufree.h"
#include "toolbox.h"

#define BEGIN     (1)
#define DATA      (2)
#define END       (3)
#define DONE      (4)

static int      UUxlat [256];           /* character translation table */
static int      UUxlen [64];            /* line length table */
static char     save[3][256];           /* buffer for decoding */
static int      B64xlat[256];
static int      XXxlat [256];

static int _pre_B64xlat[64] = {
  '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', '+', '/'
};

static int _pre_XXxlat[64] = {
  '+', '-', '0', '1', '2', '3', '4', '5',
  '6', '7', '8', '9', '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'
};

void init_conc(void)
{
  int i, j;

  /* prepare decoding translation table */
  for(i = 0; i < 256; i++)
    UUxlat[i] = B64xlat[i] = XXxlat[i] = -1;

  /*
   * At some time I received a file which used lowercase characters for
   * uuencoding. This shouldn't be, but let's accept it. Must take special
   * care that this doesn't break xxdecoding.
   */

  for(i = ' ', j = 0; i < ' ' + 64; i++, j++)
    UUxlat[i] = UUxlat[i+64] = j;

  /* add special cases */
  UUxlat['`'] = UUxlat[' '];
  UUxlat['~'] = UUxlat['^'];

  /* prepare line length table */
  UUxlen[0] = 1;
  for(i = 1, j = 5; i <= 60; i += 3, j += 4)
    UUxlen[i] = UUxlen[i+1] = UUxlen[i+2] = j;

  /* prepare Base64 Table */
  for (i=0; i<64; i++) {
    B64xlat[_pre_B64xlat[i]] = i;
    XXxlat [_pre_XXxlat [i]] = i;
  }
}

static int _uustrlen (char *string)
{
  int result=0;

  while (*string && *string != 0x0a && *string != 0x0d) {
    result++;
    string++;
  }

  return result;
}

int valid_data (char *ptr)
{
  int i, j, len;
  char *s = ptr;

  if((s == NULL) || (*s < '\0')) {
    return(0);              /* bad string */
  }
  if ((i = len = _uustrlen(s)) == 0)
    return 0;

 _t_B64:                    /* Base64 Test */
  len = i; s = ptr;

  if (len%4)
    goto _t_UU;

  while (len--) {
    if (*s < 0 || (B64xlat[*s] == -1 && *s != '='))
      goto _t_UU;
    else if (*s == '=') {   /* special case at end */
      if (len!=0 && (len!=1 || *(s+1)!='='))
	goto _t_UU;
    }
    s++;
  }
  return B64ENCODED;

 _t_UU:
  len = i; s = ptr;

  if (UUxlat[*s] == -1)     /* uutest */
    goto _t_XX;

  j = UUxlen[UUxlat[*s]];

  if (len-1 == j)	    /* remove trailing character */
    len--;
  if (len != j) {
    switch (UUxlat[*s]%3) {
    case 1:
      if (j-2 == len) j-=2;
    case 2:
      if (j-1 == len) j-=1;
      break;
    }
  }
  /*
   * some encoders are broken with respect to encoding the last line of
   * a file and produce extraoneous characters beyond the expected EOL
   * So were not too picky here about the last line, as long as it's longer
   * than necessary and shorter than the maximum
   * this tolerance broke the xxdecoding, because xxencoded data was
   * detected as being uuencoded :( so don't accept 'h' as first character
   */
  if (len != j && 
      !(*ptr != 'M' && *ptr != 'h' && len > j && len <= UUxlen[UUxlat['M']]))
    goto _t_XX;             /* bad length */

  while(len--) {
    if(*s < 0 || UUxlat[*s++] < 0) {
      goto _t_XX;           /* bad code character */
    }
  }
  return UU_ENCODED;        /* data is valid */

 _t_XX:                     /* XX Test */
  len = i; s = ptr;

  if (XXxlat[*s] == -1)
    return 0;

  j = UUxlen[XXxlat[*s]];   /* Same line length table as UUencoding */

  if (len-1 == j)	    /* remove trailing character */
    len--;
  if (len != j)
    switch (UUxlat[*s]%3) {
    case 1:
      if (j-2 == len) j-=2;
    case 2:
      if (j-1 == len) j-=1;
      break;
    }
  /*
   * some encoders are broken with respect to encoding the last line of
   * a file and produce extraoneous characters beyond the expected EOL
   * So were not too picky here about the last line, as long as it's longer
   * than necessary and shorter than the maximum
   */
  if (len != j && !(*ptr != 'h' && len > j && len <= UUxlen[UUxlat['h']]))
    return 0;               /* bad length */

  while(len--) {
    if(*s < 0 || XXxlat[*s++] < 0) {
      return 0;             /* bad code character */
    }
  }
  return XX_ENCODED;        /* data is valid */
}

int decode (char *s, char *d, int method)
{
  int i, j, c, cc, count=0;
  int *table;

  if (method == UU_ENCODED || method == XX_ENCODED) {
    if (method == UU_ENCODED)
      table = UUxlat;
    else
      table = XXxlat;

    i = table [*s++];
    j = UUxlen[i] - 1;

    while(j > 0) {
      c  = table[*s++] << 2;
      cc = table[*s++];
      c |= (cc >> 4);

      if(i-- > 0)
	d[count++] = c;
      
      cc <<= 4;
      c    = table[*s++];
      cc  |= (c >> 2);
      
    if(i-- > 0)
      d[count++] = cc;
      
      c <<= 6;
      c |= table[*s++];
      
      if(i-- > 0)
	d[count++] = c;
      
      j -= 4;
    }
  }
  else if (method == B64ENCODED) {
    while (B64xlat[*s] != -1) {
      d[count++] = (B64xlat[*s] << 2) | (B64xlat[*(s+1)] >> 4);

      if (*(s+2) != '=')
	d[count++] = (B64xlat[*(s+1)] << 4) | (B64xlat[*(s+2)] >> 2);

      if (*(s+3) != '=')
	d[count++] = (B64xlat[*(s+2)] << 6) | (B64xlat[*(s+3)]);

      s += 4;
    }
  }

  return count;
}

int uunconc (FILE *datain, FILE *dataout, int *state, long maxpos, int method)
{
  int count, tc=0, tf=0, iv=0, ic=0, vflag, haddata=0, nflag;
  char line[255], oline[400], *ptr;
  static long yctr=0;

  while (!feof (datain) && *state != DONE && (fast || ftell(datain)<maxpos)){
    if (fgets (line, 255, datain) == NULL)
      break;
    if (line[0]=='\x0d' || line[0]=='\x0a') { /* Empty line? */
      if (*state == DATA &&
	  (method == UU_ENCODED || method == XX_ENCODED))
	*state = END;
      continue;
    }
      
    /*
     * If in Windows, yield some time to other apps.
     * You call that multitasking?
     */
#ifdef _WINDOWS
    if ((++yctr % 100) == 0)
      _wyield ();
#endif      

    line[255] = '\0'; /* For Safety of string functions */
    count     =  0;

    if (strncmp (line, "BEGIN", 5) == 0 &&
	strstr  (line, "CUT HERE")      && !tf) { /* I hate these lines */
      tc = 0;
      continue;
    }
    if (method  == B64ENCODED && haddata &&         /* MIME-End Of Body */
	line[0] == '-' && line[1] == '-')
      *state = DONE;

    if (*state == BEGIN) {
      if (strncmp      (line, "begin ",       6) == 0 ||
	  _FP_strnicmp (line, "<pre>begin ", 11) == 0) {    /* for LYNX */
        iv = ic = 0;
	*state = DATA;
      }
      continue;
    }
    else if ((*state == END) &&
	     (method == UU_ENCODED || method == XX_ENCODED)) {
      if (strncmp (line, "end", 3) == 0)
	*state = DONE;
    }
    if (*state == DATA || *state == END) {
      /*
       * Netscape-Repair code is the same as in uuio.c
       */
      if ((vflag = valid_data (line)) == 0) { /* Can we Netscape-repair? */
	nflag = BrokenByNetscape (line);

	while (vflag == 0 && nflag) {
	  if (nflag == 1) {                   /* need next line to repair */
	    ptr = line + strlen (line);
	    while (ptr>line && (*(ptr-1)=='\x0d' || *(ptr-1)=='\x0a'))
	      ptr--;
	    if (fgets (ptr, 255-(ptr-line), datain) == NULL)
	      break;
	  }
	  else {                       /* don't need next line to repair */
	  }
	  if (NetscapeCollapse (line)) {
	    if ((vflag = valid_data (line)) == 0)
	      nflag = BrokenByNetscape (line);
	  }
	  else
	    nflag = 0;
	}
	/*
	 * Sometimes, a line is garbled even without it being split into
	 * the next line. Then we try this in our despair
	 */
	if (vflag == 0) {
	  if (NetscapeCollapse (line))
	    vflag = valid_data (line);
	}
      }
      if (vflag == method) {
	if (tf)
	  count  = decode (line, oline, method);
	else if (tc == 3) {
	  count  = decode (save[0], oline,         method);
	  count += decode (save[1], oline + count, method);
	  count += decode (save[2], oline + count, method);
	  count += decode (line,    oline + count, method);
	  tf     = 1;
	  tc     = 0;
	}
	else {
	  strcpy (save[tc++], line);
	}
	if (method == UU_ENCODED)
	  *state = (line[0] == 'M') ? DATA : END;
	else if (method == XX_ENCODED)
	  *state = (line[0] == 'h') ? DATA : END;
	else if (method == B64ENCODED)
	  *state = (strchr (line, '=') == NULL) ? DATA : DONE;
      }
      else {
	tf = tc = 0;
        if (iv == 0) iv = ic = 1;
        else         ic++; 
      }
    }
    else if (*state != DONE) {
      uuerror = DECOERR;
      return -1;
    }
    if (count) {
      if (fwrite (oline, 1, count, dataout) != count) {
	uuerror = FILEERR;
	return -1;
      }
      haddata++;
      count = 0;
    }
  }

  if (*state == DONE) {
    for (tf=0; tf<tc; tf++) 
      count += decode (save[tf], oline + count, method);
    if (count)
      if (fwrite (oline, 1, count, dataout) != count) {
        uuerror = FILEERR;
        return -1;
      }
    return 1;
  }
  return 0;
}

int uudecode (uulist *data)
{
  FILE *datain, *dataout;
  int state = BEGIN, res, part = -1;
  uufile *iter;


  if (data == NULL || data->thisfile == NULL) {
    uuerror = ILLVAL;
    return 0;
  }
  if (data->state & (ST_DECOERR | ST_DECODED)) {
    uuerror = OK;
    return 0;
  }

  if ((data->binfile = tempnam (NULL, "uu")) == NULL) {
    uuerror = NOMEM;
    return 0;
  }
  if ((dataout = fopen (data->binfile, "wb")) == NULL) {
    free (data->binfile);
    data->binfile = NULL;
    uuerror = FILEERR;
    return 0;
  }
  if (data->uudet == B64ENCODED) /* no begin for Base64 */
    state = DATA;

  iter = data->thisfile;
  while (iter) {
    if (part != -1 && iter->partno != part+1)
      break;
    else
      part = iter->partno;

    if (iter->data->sfname == NULL) {
      iter = iter->NEXT;
      continue;
    }
    if ((datain = fopen (iter->data->sfname, "rb")) == NULL) {
      iter = iter->NEXT;
      continue;
    }

    fseek         (datain, iter->data->startpos, SEEK_SET);
    res = uunconc (datain, dataout, &state,
		   iter->data->startpos+iter->data->length, data->uudet);
    fclose        (datain);

    if (state == DONE || res == -1)
      break;
    
    iter = iter->NEXT;
  }
  if (res == 0 && state == DATA && data->uudet == B64ENCODED)
    state = DONE; /* assume we're done ready */

  fclose (dataout);

  if (state != DONE && !desperate) {
    unlink (data->binfile);
    free   (data->binfile);
    data->binfile = NULL;
    data->state   = ST_DECOERR;
    uuerror       = INCOMPLETE;
    return 0;
  }

  data->state |= ST_DECODED;

  return 1;
}
