/*
**
** Copyright (C) 1994 Swedish University Network (SUNET)
**
**
** This program is developed by UDAC, Uppsala University by commission
** of the Swedish University Network (SUNET). 
**
** 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 FITTNESS 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 this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
**
**                                        Martin.Wendel@udac.uu.se
**                                        Torbjorn.Wictorin@udac.uu.se
**
**                                        UDAC	
**                                        P.O. Box 174
**                                        S-751 04 Uppsala
**                                        Sweden
**
*/


#include "emil.h"

unsigned char thqx[] =
        "!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr";

int fhqx[] = 
{
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, SKIP, FAIL, FAIL, SKIP, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
  0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, FAIL, FAIL,
  0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, FAIL,
  0x14, 0x15, DONE, FAIL, FAIL, FAIL, FAIL, FAIL,
  0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
  0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, FAIL,
  0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, FAIL,
  0x2C, 0x2D, 0x2E, 0x2F, FAIL, FAIL, FAIL, FAIL,
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, FAIL,
  0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, FAIL, FAIL,
  0x3D, 0x3E, 0x3F, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
  FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL
};

int
encode_binhex(struct message *m)
{
  struct data *inbuf, *outbuf;
  int linelen;
  int i, left;
  int run = 0;
  unsigned long triple;
  unsigned char *inb;
  
  inbuf = m->td;

  outbuf = (struct data *)Yalloc(sizeof(struct data ));
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "Encoding BinHex\n");
#endif
  /* Exit on empty input */
  if (!inbuf->size)
    {
#ifdef DEBUG
      if (edebug)
	fprintf(stderr, ", no input (failed).\n");
#endif
      return(NOK);
    }
  macify_filename(m);
  /* Create binhex binary file */
  if (create_binhex_binary(m) != OK)
    return(NOK);
  inbuf = m->td;
  run = 0;
  linelen = 0;

  outbuf->encoding = EBINHEX;
  /* Initialize working pointers */
  inb = inbuf->contents + inbuf->offset;
  i = 0;
  triple = 0;
  /*
   * Add preamble
   */
  append_data(outbuf, "(This file must be converted with BinHex 4.0)\n:", 47, pz);
  linelen = 1;
  outbuf->lineend += 1;
  /*
   * Process entire inbuf.
   */
  left = inbuf->bodyend - inbuf->offset;
  while (left > 0 || i > 0)
    {
      if (left > 0)
	{
	  triple = (triple <<8) | *inb;
	  left--;
	  i++;
	  /* Handle special case of run length encoding */
	  if (*inb == 0x90)
	    {
	      if (i < 3)
		{
/*
#ifdef DEBUG
		  if (edebug)
		    fprintf(stderr, "*   DOING A RUN 0 AT %d.\n", outbuf->bodyend);
#endif
*/
		  triple = (triple <<8) ;
		  i++;
		}
	      else
		run = 1;
	    }
	  inb++;
	  inbuf->offset += 1;
	}
      if (i == 3 || left == 0)
	{
	  switch (i)
	    {
	    case 1:
	      triple = triple<<4;
	      break;
	    case 2:
	      triple = triple<<2;
	      break;
	    default :
	      break;
	    }
	  for (; i >= 0; i--)
	    {
	      append_char(outbuf, thqx[0x3f & (triple>>(6*i))], pz);
	      if (linelen == 64)
		{
		  append_char(outbuf, '\n', pz);
		  outbuf->lineend += 1;
		  linelen = 0;
		}
	      else
		{
		  linelen++;
		}
	    }
	  triple = 0;
	  i = 0;
	}
      if (run == 1 && i == 0)
	{
/*
	  fprintf(stderr, "*   DOING A EXTRA RUN 0 AT %d.\n", outbuf->bodyend);
*/
	  triple = (triple <<8) ;
	  i++;
	  run = 0;
	}
    }
  if (linelen == 0)
    {
      outbuf->end -= 1;
    }
  append_data(outbuf, ":\n", 2, pz);
  outbuf->lineend += 1;
  safe_mchange(m, outbuf);
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, ", done.\n");
#endif
  return(OK);
}

int
decode_binhex(struct message *m)
{
  struct data *inbuf, *outbuf;
  char *inb;
  unsigned int i;
  int l;
  int dl = 0;
  int left;
  unsigned short run = 0;
  unsigned char crun, lrun = '\0';
  unsigned long triple;

  inbuf = m->td;
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "*  Decode BinHex\n");
#endif
  inbuf->offset = inbuf->bodystart;
  logger(LOG_DEBUG, "decode binhex");
  /* Exit on empty input */
  if (!inbuf->size)
    return(NOK);

  /* Initialize working pointers */
  inb = inbuf->contents + inbuf->offset;
  outbuf = (struct data *)Yalloc(sizeof(struct data));
  outbuf->encoding = EBINARY;

  l = 0;

  /* Look for preamble */
  while (isspace(*inb))
    {
      inb++;
      inbuf->offset += 1;
    }
  if (strncmp(inb, "(This file must be converted with BinHex", 40) != 0)
    {
      logger(LOG_WARNING, "decode_binhex: BinHex preamble error");
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, ", no preamble (failed).\n");
#endif
      return(NOK);
    }
  inb += 40;
  inbuf->offset += 40;
  while (*inb != '\n')
    {
      inb++;
      inbuf->offset += 1;
    }
  while (1)
    {
      int tmpi;
      tmpi = fhqx[(unsigned char)*inb];
      if (tmpi != SKIP)
	break;
      inb++;
      inbuf->offset += 1;
    }
  if (*inb != ':')
    {
      logger(LOG_WARNING, "decode_binhex: BinHex: No starting colon");
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, ", no starting colon (failed).\n");
#endif
      return(NOK);
    }
      
  inb++;
  inbuf->offset += 1;
  /*
   * Process entire inbuf.
   */
  left = inbuf->end - inbuf->offset;
  triple = 0;
  while (left != 0)
    {
      left--;
      i = fhqx[(unsigned char)*inb];
      switch(i)
	{
	case FAIL:
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, ", illegal character at %lu : %c (failed).\n",
		    inbuf->offset, *inb);
#endif
	  sprintf(ebuf, "ERROR: BinHex: Illegal character: %c\n", *inb);
	  logger(LOG_WARNING, ebuf);
	  return(NOK);
	case SKIP:
	  break;
	case DONE:
	  inb++;
	  inbuf->offset += 1;
	  if (*inb != '\n' &&
	      *inb != '\r')
	    return(NOK);
	  left = 0;
	  break;
	default:
	  triple = triple<<6 | (0x3f & i);
	  l++;
	  break;
	}
      if (l == 4 || left == 0)
	{
	  switch(l)
	    {
	    case 2:
	      triple = triple>>4;
	      break;
	    case 3:
	      triple = triple>>2;
	      break;
	    default:
	      break;
	    }
	  if (process || dl < 300)
	    for (l -= 2; l >= 0; l--)
	      {
		/* Handle run length encoding */
		crun = 0xff & (triple>>(l*8));
/*
   if (crun > 32 && crun < 128)
   fprintf(stderr, "PROCESSING: %c\n", crun);
   else
   fprintf(stderr, "PROCESSING: <%X>\n", crun);
*/

		switch (run)
		  {
		  case 0:
		    if (crun == 0x90)
		      {
/*
   fprintf(stderr, "DETECTED RUN at %d\n", inbuf->offset);
*/
			run = 1;
		      }
		    else
		      {
/*
   if (crun > 31 && crun < 128)
   fprintf(stderr, "PRINTING: %c\n", crun);
   else
   fprintf(stderr, "PRINTING: <%X>\n", crun);
*/
			append_char(outbuf, crun, pz);
			dl++;
			lrun = crun;
		      }
		    break;
		  case 1:
		    if (crun == 0)
		      {
/*
   fprintf(stderr, "RUNNING RUN 0 at %d\n", inbuf->offset);
*/
			append_char(outbuf, 0x90, pz);
			dl++;
			lrun = 0x90;
		      }
		    else
		      {
			run = crun;

/*
   fprintf(stderr, "RUNNING RUN %d at %d\n", crun, inbuf->offset);
*/
			while (run > 1)
			  {
/*
   if (crun > 31 && crun < 128)
   fprintf(stderr, "PRINTING: %c\n", lrun);
   else
   fprintf(stderr, "PRINTING: <%X>\n", lrun);
*/
			    append_char(outbuf, lrun, pz);
			    dl++;
			    run--;
			  }
		      }
		    run = 0;
		    break;
		  default:
		    /* This should neven occur */
#ifdef DEBUG
		    if (edebug)
		      fprintf(stderr, ", Confused state...");
#endif
		    logger(LOG_DEBUG, "What?: decode_binhex: This should neven occur");
		    break;
		  }
	      }
	  triple = 0;
	  l = 0;
	}
      inb++;
      inbuf->offset += 1;
    }
  if (process)
    {
      safe_mchange(m, outbuf);
      if (get_binhex_binary(m) == OK)
	{
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, ", done.\n");
#endif
	  if (match(m->sd->type, "TEXT"))
	    {
	      outbuf->encoding = E7BIT;
	      check_bits(m->td);
	    }
	  else
	    outbuf->encoding = EBINARY;
	  return(OK);
	}
      else
	{
	  m->td = m->sd;
	  return(NOK);
	}
    }
  else
    {
      if (dl >= 300)
	{
	  m->td = outbuf;
	  get_binhex_binary(m);
	  m->td = m->sd;
	}
#ifdef DEBUG
      if (edebug)
	fprintf(stderr, ", done.\n");
#endif
      return(OK);
    }
}

