/*
**
** 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"
#include <assert.h>

/*
 * ntohl but possibly unaligned
*/
static	unsigned long	getlong(unsigned char * a, int l)
{
	assert(l == 4);
	
	return 	((unsigned long) a[0] <<24) 	|
		((unsigned long) a[1] <<16) 	|
		((unsigned long) a[2] <<8) 	|
		((unsigned long) a[3]) 		;
}

/*
**	Calculates the CRC of a BinHex file
**
*/

unsigned long
calc_crc(unsigned long crc,unsigned int c)
{
  register int i;
  register unsigned long tmk;

  tmk = crc;
  for (i = 0; i < 8; i++)
    {
      c <<= 1;
      if ((tmk <<= 1) & 0x10000)
	tmk = (tmk & 0xffff) ^ 0x1021;
      tmk ^= (c >> 8);
      c &= 0xff;
    }

  assert(tmk <= 65535); /* ELSE TRUNC / TW */
  return((unsigned short)tmk);
}

/*
**
**	Extract and check the CRC of a BinHex file.
**
*/

int
get_binhex_crc(struct data *d, unsigned long checksum)
{
  int partlen;
  /* CRC for the CRC */
  checksum = calc_crc(checksum, (unsigned char)'\0');
  checksum = calc_crc(checksum, (unsigned char)'\0');

  if (d->offset + 2 > d->end)
    {
      logger(LOG_WARNING, "get_binhex_crc: Premature end of data in BinHexed file");
      return(NOK);
    }
  for (partlen = 1; partlen >= 0; partlen--, d->offset += 1)
    {
      if (((0xFF & *(d->contents + d->offset)) ^ (0xFF & (checksum >> (8*partlen))))
	  != 0)
	  return(NOK);
    }
  return(OK);
}

/*
**
**	Extract header from a BinHex file
**
*/

int
get_binhex_binary(struct message *m)
{
  unsigned long checksum;
  unsigned int filenamelen;
  char flags[3];
  struct data *d;
  int partlen;
  int rlen;
  char cdlen[5];


  d = m->td;
  checksum = 0;

  if (d->contents == NULL)
    {
      logger(LOG_WARNING, "get_binhex_binary: getting namelen: called with empty data");
      return(NOK);
    }

  /* Extract filenamelen */
  filenamelen = *(d->contents + d->offset);
  checksum = calc_crc(0, (unsigned char) *(d->contents + d->offset));
  d->offset += 1;

  /* Extract name from a BinHex file */
  m->name = (char *)Yalloc(filenamelen + 1);
  if (d->offset + filenamelen > d->end)
    {
      logger(LOG_WARNING, "get_binhex_binary: getting name: premature end of data");
      return(NOK);
    }
  assert (filenamelen > 0);
  bcopy(d->contents + d->offset, m->name, (unsigned) filenamelen);
  for (partlen = 0; partlen < filenamelen; partlen++, d->offset += 1)
    checksum = calc_crc(checksum, *(d->contents + d->offset));

  if (*(d->contents + d->offset) != '\0')
    {
      logger(LOG_WARNING, "get_binhex_binary: Mandantory null byte unavailable in binhex header");
      m->td = m->sd;
      return(NOK);
    }

  /* Move past NULL */
  checksum = calc_crc(checksum, (unsigned char)'\0');
  d->offset += 1;


  /* Extract type of a BinHex file */
  if ((d->offset + 4) > d->end)
    {
      m->td = m->sd;
      return(NOK);
    }
  bzero(m->appletype, 9);
  bcopy(d->contents + d->offset, m->appletype, 4);
  for (partlen = 0; partlen < 4; partlen++, d->offset += 1)
    checksum = calc_crc(checksum, *(d->contents + d->offset));


  /* Extract auth of a BinHex file */
  if ((d->offset + 4) > d->end)
    {
      m->td = m->sd;
      return(NOK);
    }
  bcopy(d->contents + d->offset, m->appletype + 4, 4);
  for (partlen = 0; partlen < 4; partlen++, d->offset += 1)
    checksum = calc_crc(checksum, *(d->contents + d->offset));

  if ((m->type = confextr("APPLEFILE", m->appletype, NULL)) == NULL)
    if ((m->type = confextr("APPLEFILE", "DEFAULT", NULL)) == NULL)
      m->type = NEWSTR("APPLICATION");

  /* Extract flags of a BinHex file. */
  if ((d->offset + 2) > d->end)
    {
      m->td = m->sd;
      return(NOK);
    }
  /* These might just as well be junked */
  bzero(flags, 3);
  bcopy(d->contents + d->offset, flags, 2);
  for (partlen = 0; partlen < 2; partlen++, d->offset += 1)
    checksum = calc_crc(checksum, *(d->contents + d->offset));


  /* Extract datafork length of a BinHex file. */
  if ((d->offset + 4) > d->end)
    {
      m->td = m->sd;
      return(NOK);
    }
  bzero(cdlen, 5);
  bcopy(d->contents + d->offset, cdlen, 4);
  for (partlen = 0; partlen < 4; partlen++, d->offset += 1)
    checksum = calc_crc(checksum, *(d->contents + d->offset));
  d->length = getlong(cdlen, 4);


  /* Extract resource fork length of a BinHex file. */
  /* This really not interesting until we support resource forks */
  if ((d->offset + 4) > d->end)
    {
      m->td = m->sd;
      return(NOK);
    }
  bzero(cdlen, 5);
  bcopy(d->contents + d->offset, cdlen, 4);
  for (partlen = 0; partlen < 4; partlen++, d->offset += 1)
    checksum = calc_crc(checksum, *(d->contents + d->offset));
  rlen = getlong(cdlen, 4);

  /* Check header CRC */
  if (get_binhex_crc(d, checksum) != OK)
    {
      logger(LOG_WARNING, "get_binhex_binary: CRC Error in header");
      m->td = m->sd;
      return(NOK);
    }


  /* Extract and output the datafork */
  if (process)
    {
      char *tmpchar;
      d->bodystart = d->offset;
      tmpchar = d->contents + d->bodystart;
      d->bodyend = d->offset + d->length;
    }
  if (d->offset + d->length >= d->end)
    {
      logger(LOG_WARNING, "get_binhex_binary: getting datafork: premature end of data");
      m->td = m->sd;
      return(NOK);
    }
  checksum = 0;
  for(partlen = 0; partlen < d->length; partlen++, d->offset += 1)
    {
      checksum = calc_crc(checksum, (unsigned char)*(d->contents + d->offset));
    }
  if (get_binhex_crc(d, checksum) != OK)
    {
      logger(LOG_WARNING, "get_binhex_binary: CRC Error in data fork");
      m->td = m->sd;
      return(NOK);
    }


  /* Extract and junk the resource fork */
  if (d->offset + rlen >= d->end)
    {
      logger(LOG_WARNING, "get_binhex_binary: getting resource fork: premature end of data");
      m->td = m->sd;
      return(NOK);
    }
  checksum = 0; 
  for(partlen = 0; partlen < rlen; partlen++, d->offset += 1)
    {
      checksum = calc_crc(checksum, (unsigned char)*(d->contents + d->offset));
    }
  if (get_binhex_crc(d, checksum) == OK)
    {
      return(OK);
    }
  else
    {
      logger(LOG_WARNING, "get_binhex_binary: CRC Error in resource fork");
      m->td = m->sd;
      return(NOK);
    }
}


int
create_binhex_binary(struct message *m)
{
  /* unsigned long checksum; << UNUSED / TW */
  unsigned long filenamelen;
  char four[5];
  struct data *d, *h;
  unsigned long crc;
  long dlen, rlen;
  long partlen;


  h = (struct data *) Yalloc(sizeof(struct data ));
  d = m->td;

  if (m->name == NULL)
    m->name = NEWSTR("noname");

  filenamelen = strlen(m->name);

  /* Add filenamelen */
  append_char(h, (char) (0xff & filenamelen));
  
  /* Add filename */
  append_data(h, m->name, filenamelen);
  
  /* Add mandantory NULL byte */
  append_char(h, '\0');
  
  /* Add type */
  
  append_data(h, m->appletype, 4);

  /* Add auth */
  append_data(h, m->appletype + 4, 4);

  /* Add flags */
  append_data(h, "\0\0\0", 2);

  /* Add data fork length */
  dlen = d->bodyend - d->bodystart;
  bzero(four, 5);
  for (partlen = 4; partlen > 0; partlen--)
    {
      four[4 - partlen] = (char) 0xff & (dlen >> ((partlen - 1) * 8));
    }
  append_data(h, four, 4);

  /* Add resource fork length */
  bzero(four, 5);
  rlen = 0; /* Resource fork is empty */
  for (partlen = 4; partlen > 0; partlen--)
    {
      four[4 - partlen] = (char) 0xff & (rlen >> ((partlen - 1) * 8));
    }
  append_data(h, four, 4);

  /* Generate header CRC */
  crc = 0;
  for (partlen = 0; partlen < h->bodyend; partlen++)
    {
      crc = calc_crc(crc, *(h->contents + partlen));
    }
  crc = calc_crc(crc, '\0');
  crc = calc_crc(crc, '\0');
  bzero(four, 5);
  for (partlen = 2; partlen > 0; partlen--)
    {
      four[2 - partlen] = (char) 0xff & (crc >> ((partlen - 1) * 8));
    }
  append_data(h, four, 2);


  /* Append data fork */
  append_data(h, d->contents + d->bodystart, d->bodyend - d->bodystart);

  /* Append data CRC */
  crc = 0;
  for (partlen = h->bodystart; partlen < h->bodyend; partlen++)
    {
      crc = calc_crc(crc, *(h->contents + partlen));
    }
  crc = calc_crc(crc, '\0');
  crc = calc_crc(crc, '\0');
  bzero(four, 5);
  for (partlen = 2; partlen > 0; partlen--)
    {
      four[2 - partlen] = (char) 0xff & (crc >> ((partlen - 1) * 8));
    }
  append_data(h, four, 2);

  /* Append resource CRC */
  crc = 0;
  crc = calc_crc(crc, '\0');
  crc = calc_crc(crc, '\0');
  bzero(four, 5);
  for (partlen = 2; partlen > 0; partlen--)
    {
      four[2 - partlen] = (char) 0xff & (crc >> ((partlen - 1) * 8));
    }
  append_data(h, four, 2);
  m->td = h;
  return(OK);
}



/*
 * Check for AppleSingle/AppleDouble
 */

int
get_file_type(struct data *d)
{
  char typer[5];
  long ltyper;
  if (d->offset + 4 >= d->end)
    {
      return(TYPEUNKNOWN);
    }
  bzero(typer, 5);
  bcopy(d->contents + d->offset, typer, 4);
  ltyper = strtol(typer, NULL, 16);
  if (0x0051607 ==  ltyper)
    return(TYPEAPPLEDOUBLE);
  if (0x0051600 ==  ltyper)
    return(TYPEAPPLESINGLE);
  return(TYPEUNKNOWN);
}

