/*----------------------------------------------------------------------*/
/*	header.c (from lharc.c)	-- header manipulate functions		*/
/*	Original					by Y.Tagawa	*/
/*	modified  Dec 16 1991				by M.Oki	*/
/*----------------------------------------------------------------------*/

#include "lharc.h"
extern int header_level;

int
calc_sum (register char *p, register int len)
{
  register int sum;

  for (sum = 0; len; len --)
    sum += *p++;

  return sum & 0xff;
}

static char *get_ptr;
#define setup_get(PTR)		(get_ptr = (PTR))
#define get_byte()		(*get_ptr++ & 0xff)
#define put_ptr			get_ptr
#define setup_put(PTR)		(put_ptr = (PTR))
#define put_byte(c)		(*put_ptr++ = (char)(c))

static unsigned short
get_word (void)
{
  int b0, b1;

  b0 = get_byte ();
  b1 = get_byte ();
  return (unsigned short)((b1 << 8) + b0);
}

static void
put_word (unsigned int v)
{
  put_byte (v);
  put_byte (v >> 8);
}

static long
get_longword (void)
{
  long b0, b1, b2, b3;

  b0 = get_byte ();
  b1 = get_byte ();
  b2 = get_byte ();
  b3 = get_byte ();
  return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
}

static void
put_longword (long v)
{
  put_byte (v);
  put_byte (v >> 8);
  put_byte (v >> 16);
  put_byte (v >> 24);
}


static void
msdos_to_unix_filename (register char *name, register int len)
{
  register int i;

#ifdef MULTIBYTE_CHAR
  for (i = 0; i < len; i ++)
    {
      if (MULTIBYTE_FIRST_P (name[i]) &&
	  MULTIBYTE_SECOND_P (name[i+1]))
	i ++;
      else if (name[i] == '\\')
	name[i] = '/';
      else if (isupper (name[i]))
	name[i] = tolower (name[i]);
    }
#else

  for (i = 0; i < len; i ++)
    {
      if (name[i] == '\\')
	name[i] = '/';
      else if (isupper (name[i]))
	name[i] = tolower (name[i]);
    }
#endif
}

static void
generic_to_unix_filename (register char *name, register int len)
{
  register int i;
  boolean lower_case_used = FALSE;

#ifdef MULTIBYTE_CHAR
  for (i = 0; i < len; i ++)
    {
      if (MULTIBYTE_FIRST_P (name[i]) &&
		  MULTIBYTE_SECOND_P (name[i+1]))
		i ++;
      else if (islower (name[i]))
		{
		  lower_case_used = TRUE;
		  break;
		}
    }
  for (i = 0; i < len; i ++)
    {
      if (MULTIBYTE_FIRST_P (name[i]) &&
		  MULTIBYTE_SECOND_P (name[i+1]))
		i ++;
      else if (name[i] == '\\')
		name[i] = '/';
      else if (!lower_case_used && isupper (name[i]))
		name[i] = tolower (name[i]);
    }
#else
  for (i = 0; i < len; i ++)
    if (islower (name[i]))
      {
		lower_case_used = TRUE;
		break;
      }
  for (i = 0; i < len; i ++)
    {
      if (name[i] == '\\')
		name[i] = '/';
      else if (!lower_case_used && isupper (name[i]))
		name[i] = tolower (name[i]);
    }
#endif
}

static void
macos_to_unix_filename (register char *name, register int len)
{
  register int i;

  for (i = 0; i < len; i ++)
    {
      if (name[i] == ':')
	name[i] = '/';
      else if (name[i] == '/')
	name[i] = ':';
    }
}

static void
unix_to_generic_filename (register char *name, register int len)
{
  register int i;

  for (i = 0; i < len; i ++)
    {
      if (name[i] == '/')
		name[i] = '\\';
      else if (islower (name[i]))
		name[i] = toupper (name[i]);
    }
}

/*----------------------------------------------------------------------*/
/*									*/
/*	Generic stamp format:						*/
/*									*/
/*	 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16		*/
/*	|<-------- year ------->|<- month ->|<-- day -->|		*/
/*									*/
/*	 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0		*/
/*	|<--- hour --->|<---- minute --->|<- second*2 ->|		*/
/*									*/
/*----------------------------------------------------------------------*/

/* NOTE : If you don't have `gettimeofday(2)', or your gettimeofday(2)
   returns bogus timezone information, try FTIME, MKTIME, TIMELOCAL or
   TZSET. */

/* choose one */
#if defined(MKTIME)
#ifdef TIMELOCAL
#undef TIMELOCAL
#endif
#endif /* defined(MKTIME) */

#if defined(MKTIME) || defined(TIMELOCAL)
#ifdef TZSET
#undef TZSET
#endif
#endif /* defined(MKTIME) || defined(TIMELOCAL) */

#if defined(MKTIME) || defined(TIMELOCAL) || defined(TZSET)
#ifdef FTIME
#undef FTIME
#endif
#endif

#if defined(MKTIME) || defined(TIMELOCAL) || defined(TZSET) || defined(FTIME)
#ifdef GETTIMEOFDAY
#undef GETTIMEOFDAY
#endif
#else
#ifndef GETTIMEOFDAY
#define GETTIMEOFDAY	/* use gettimeofday() */
#endif
#endif

#ifdef FTIME
#include <sys/timeb.h>
#endif

/* You may define as : 
	#define TIMEZONE_HOOK		\
		extern long timezone ;	\
		extern void tzset();
*/
#ifdef TIMEZONE_HOOK
TIMEZONE_HOOK
/* Which do you like better, `TIMEZONE_HOOK' or `TIMEZONE_HOOK;' ? */
#endif

#if defined(TZSET) && defined(_MINIX)
extern long timezone;	/* not defined in time.h */
#endif

#if defined(FTIME) || defined(GETTIMEOFDAY) || defined(TZSET)
static long
gettz (void)
#ifdef TZSET
{
   tzset();
   return timezone;
}
#endif
#if !defined(TZSET) && defined(FTIME)
{
   struct timeb buf;

   ftime(&buf);
   return buf.timezone * 60L;
}
#endif
#if !defined(TZSET) && !defined(FTIME) /* maybe defined(GETTIMEOFDAY) */
{
   struct timeval tp;
   struct timezone tzp;
   gettimeofday (&tp, &tzp);	/* specific to 4.3BSD */
/* return (tzp.tz_minuteswest * 60L + (tzp.tz_dsttime != 0 ? 60L * 60L : 0));*/
   return (tzp.tz_minuteswest * 60L);
}
#endif
#endif /* defined(FTIME) || defined(GETTIMEOFDAY) || defined(TZSET) */

#ifdef NOT_USED
static struct tm *
msdos_to_unix_stamp_tm (long a)
{
  static struct tm t;

  t.tm_sec	= ( a          & 0x1f) * 2;
  t.tm_min	=  (a >>    5) & 0x3f;
  t.tm_hour	=  (a >>   11) & 0x1f;
  t.tm_mday	=  (a >>   16) & 0x1f;
  t.tm_mon	= ((a >> 16+5) & 0x0f) - 1;
  t.tm_year	= ((a >> 16+9) & 0x7f) + 80;
  return &t;
}
#endif

static time_t
generic_to_unix_stamp (long t)
#if defined(MKTIME) || defined(TIMELOCAL)
{
  struct tm dostm;

  /* special case:  if MSDOS format date and time were zero, then we set
     time to be zero here too. */
  if (t == 0)
    return (time_t)0;

  dostm.tm_sec	 = (t         & 0x1f) * 2;
  dostm.tm_min	 =  t >> 5    & 0x3f;
  dostm.tm_hour	 =  t >> 11   & 0x1f;
  dostm.tm_mday	 =  t >> 16   & 0x1f;
  dostm.tm_mon	 = (t >> 16+5 & 0x0f) - 1;	/* 0..11 */
  dostm.tm_year	 = (t >> 16+9 & 0x7f) + 80;
  dostm.tm_isdst = 0;				/* correct? */
#ifdef MKTIME
  return (time_t)mktime(&dostm);
#else /* maybe defined(TIMELOCAL) */
  return (time_t)timelocal(&dostm);
#endif
}
#else /* defined(MKTIME) || defined(TIMELOCAL) */
{
  int year, month, day, hour, min, sec;
  long longtime;
  static unsigned int dsboy[12] = { 0, 31, 59, 90, 120, 151,
				      181, 212, 243, 273, 304, 334};
  unsigned int days;

  /* special case:  if MSDOS format date and time were zero, then we set
     time to be zero here too. */
  if (t == 0)
    return (time_t)0;

  year  = ((int)(t >> 16+9) & 0x7f) + 1980;
  month =  (int)(t >> 16+5) & 0x0f;	/* 1..12 means Jan..Dec */
  day   =  (int)(t >> 16)   & 0x1f;	/* 1..31 means 1st,...31st */

  hour  =  ((int)t >> 11) & 0x1f;
  min   =  ((int)t >> 5)  & 0x3f;
  sec   = ((int)t         & 0x1f) * 2;

				/* Calculate days since 1970.01.01 */
  days = (365 * (year - 1970) + /* days due to whole years */
	  (year - 1970 + 1) / 4 + /* days due to leap years */
	  dsboy[month-1] +	/* days since beginning of this year */
	  day-1);		/* days since beginning of month */

  if ((year % 4 == 0) &&
      (year % 400 != 0) &&
      (month >= 3))		/* if this is a leap year and month */
    days ++;			/* is March or later, add a day */

  /* Knowing the days, we can find seconds */
  longtime = (((days * 24) + hour) * 60 + min) * 60 + sec;
  longtime += gettz ();      /* adjust for timezone */

  /* LONGTIME is now the time in seconds, since 1970/01/01 00:00:00.  */
  return (time_t)longtime;
}
#endif /* defined(MKTIME) || defined(TIMELOCAL) */

static long
unix_to_generic_stamp (time_t t)
{
  struct tm *tm = localtime (&t);

  return ((((long)(tm->tm_year - 80)) << 25) +
	  (((long)(tm->tm_mon + 1)) << 21) +
	  (((long)tm->tm_mday) << 16) +
	  (long)((tm->tm_hour << 11) +
		 (tm->tm_min << 5) +
		 (tm->tm_sec / 2)));
}

/*----------------------------------------------------------------------*/
/*			build header functions				*/
/*----------------------------------------------------------------------*/

boolean
get_header (FILE *fp, register LzHeader *hdr)
{
  int header_size;
  int name_length;
  char data[LZHEADER_STRAGE];
  char dirname[FILENAME_LENGTH];
  int dir_length = 0;
  int checksum;
  int i;
  char *ptr;

  bzero (hdr, sizeof (LzHeader));

  if (((header_size = getc (fp)) == EOF) || (header_size == 0))
    {
      return FALSE;		/* finish */
    }

  if (fread (data + I_HEADER_CHECKSUM,
		  sizeof (char), header_size - 1, fp) < header_size - 1)
    {
      fatal_error ("Invalid header (LHarc file ?)");
      return FALSE;		/* finish */
    }
  setup_get(data + I_HEADER_LEVEL);
  hdr->header_level = get_byte();
  if (hdr->header_level != 2 &&
      fread (data + header_size, sizeof (char), 2, fp) < 2)
    {
      fatal_error ("Invalid header (LHarc file ?)");
      return FALSE;		/* finish */
    }

  setup_get (data + I_HEADER_CHECKSUM);
  checksum = get_byte();

  hdr->header_size = header_size;
  bcopy (data + I_METHOD, hdr->method, METHOD_TYPE_STRAGE);
#ifdef OLD
  if ((bcmp (hdr->method, LZHUFF1_METHOD, METHOD_TYPE_STRAGE) != 0) &&
      (bcmp (hdr->method, LZHUFF0_METHOD, METHOD_TYPE_STRAGE) != 0) &&
      (bcmp (hdr->method, LARC5_METHOD, METHOD_TYPE_STRAGE) != 0) &&
      (bcmp (hdr->method, LARC4_METHOD, METHOD_TYPE_STRAGE) != 0))
    {
      warning ("Unknown method (LHarc file ?)", "");
      return FALSE;		/* invalid method */
    }
#endif
  setup_get (data + I_PACKED_SIZE);
  hdr->packed_size	= get_longword ();
  hdr->original_size	= get_longword ();
  hdr->last_modified_stamp = get_longword ();
  hdr->attribute	= get_byte ();

  if ((hdr->header_level = get_byte ()) != 2) {
    if (calc_sum (data + I_METHOD, header_size) != checksum)
      warning ("Checksum error (LHarc file?)", "");
    name_length	= get_byte ();
    for (i = 0; i < name_length; i ++)
      hdr->name[i] =(char)get_byte ();
    hdr->name[name_length] = '\0';
  } else {
    hdr->unix_last_modified_stamp = hdr->last_modified_stamp;
    name_length = 0;
  }

  /* defaults for other type */
  hdr->unix_mode	= UNIX_FILE_REGULAR | UNIX_RW_RW_RW;
  hdr->unix_gid 	= 0;
  hdr->unix_uid		= 0;

  if (header_size - name_length >= 24)
    {				/* EXTEND FORMAT */
      hdr->crc				= get_word ();
      hdr->extend_type			= get_byte ();
      hdr->has_crc = TRUE;
    }
  else if (header_size - name_length == 22)
    {				/* Generic with CRC */
      hdr->crc				= get_word ();
      hdr->extend_type			= EXTEND_GENERIC;
      hdr->has_crc = TRUE;
    }
  else if (header_size - name_length == 20)
    {				/* Generic no CRC */
      hdr->extend_type			= EXTEND_GENERIC;
      hdr->has_crc = FALSE;
    }
  else
    {
      warning ("Unknown header (LHarc file ?)", "");
      return FALSE;
    }

  if (hdr->extend_type == EXTEND_UNIX && hdr->header_level == 0)
    {
      hdr->minor_version		= get_byte ();
      hdr->unix_last_modified_stamp	= (time_t)get_longword ();
      hdr->unix_mode			= get_word ();
      hdr->unix_uid			= get_word ();
      hdr->unix_gid			= get_word ();
      return TRUE;
    }

  if (hdr->header_level > 0) {
    /* Extend Header */
    if (hdr->header_level != 2) setup_get(data + hdr->header_size);
    ptr = get_ptr;
    while((header_size = get_word()) != 0)
      {
	if (hdr->header_level != 2 &&
	    ((data + LZHEADER_STRAGE - get_ptr < header_size) ||
	     fread(get_ptr, sizeof(char), header_size, fp) < header_size))
	  {
	    fatal_error ("Invalid header (LHa file ?)");
	    return FALSE;
	  }
	switch (get_byte()) {
	case 0:
	  /*
	   * header crc
	   */
	  setup_get(get_ptr + header_size - 3);
	  break;
	case 1:
	  /*
	   * filename
	   */
	  for (i = 0; i < header_size - 3; i++)
	    hdr->name[i] =(char)get_byte ();
	  hdr->name[header_size - 3] = '\0';
	  break;
	case 2:
	  /*
	   * directory
	   */
	  for (i = 0; i < header_size - 3; i++)
	    dirname[i] = (char)get_byte ();
	  dirname[header_size - 3] = '\0';
	  convdelim(dirname, DELIM);
	  dir_length = header_size-3;
	  break;
	case 0x40:
	  /*
	   * MS-DOS attribute
	   */
	  if (hdr->extend_type == EXTEND_MSDOS ||
	      hdr->extend_type == EXTEND_HUMAN ||
	      hdr->extend_type == EXTEND_GENERIC)
	    hdr->attribute = get_word();
	  break;
	case 0x50:
	  /*
	   * UNIX permission
	   */
	  if (hdr->extend_type == EXTEND_UNIX)
	    hdr->unix_mode = get_word();
	  break;
	case 0x51:
	  /*
	   * UNIX gid and uid
	   */
	  if (hdr->extend_type == EXTEND_UNIX)
	    {
	      hdr->unix_gid = get_word();
	      hdr->unix_uid = get_word();
	    }
	  break;
	case 0x52:
	  /*
	   * UNIX group name
	   */
	  setup_get(get_ptr + header_size - 3);
	  break;
	case 0x53:
	  /*
	   * UNIX user name
	   */
	  setup_get(get_ptr + header_size - 3);
	  break;
	case 0x54:
	  /*
	   * UNIX last modified time
	   */
	  if (hdr->extend_type == EXTEND_UNIX)
	    hdr->unix_last_modified_stamp = (time_t)get_longword();
	  break;
	default:
	  /*
	   * other headers
	   */
	  setup_get(get_ptr + header_size - 3);
	  break;
	}
      }
    if (hdr->header_level != 2 && get_ptr - ptr != 2) {
      hdr->packed_size -= get_ptr - ptr - 2;
      hdr->header_size += get_ptr - ptr - 2;
    }
  }
  if (dir_length)
    {
      strcat(dirname, hdr->name);
      strcpy(hdr->name, dirname);
      name_length += dir_length;
    }

  switch (hdr->extend_type)
    {
    case EXTEND_MSDOS:
      msdos_to_unix_filename (hdr->name, name_length);
    case EXTEND_HUMAN:
      if (hdr->header_level == 2)
	hdr->unix_last_modified_stamp = hdr->last_modified_stamp;
      else
	hdr->unix_last_modified_stamp	=
	  generic_to_unix_stamp (hdr->last_modified_stamp);
      break;

#ifdef OSK
    case EXTEND_OS68K:
    case EXTEND_XOSK:
#endif
    case EXTEND_UNIX:
      break;
 
    case EXTEND_MACOS:
      macos_to_unix_filename (hdr->name, name_length);
      hdr->unix_last_modified_stamp	=
		generic_to_unix_stamp (hdr->last_modified_stamp);
      break;

    default:
      generic_to_ados_filename (hdr->name, name_length);
      if (hdr->header_level == 2)
	hdr->unix_last_modified_stamp = hdr->last_modified_stamp;
      else
	hdr->unix_last_modified_stamp	=
	  generic_to_unix_stamp (hdr->last_modified_stamp);
    }

#ifdef AMIGA
  ados_convert(hdr);
#endif

  return TRUE;
}

void
init_header (char *name, struct stat *v_stat, LzHeader *hdr)
{
  int len;

  if ( compress_method == 5 )
  		bcopy (LZHUFF5_METHOD, hdr->method, METHOD_TYPE_STRAGE);
  else if ( compress_method )
  		bcopy (LZHUFF1_METHOD, hdr->method, METHOD_TYPE_STRAGE);
  else
		bcopy (LZHUFF0_METHOD, hdr->method, METHOD_TYPE_STRAGE);

  hdr->packed_size		= 0;
  hdr->original_size		= v_stat->st_size;
  hdr->last_modified_stamp	= unix_to_generic_stamp (v_stat->st_mtime);
  hdr->attribute		= GENERIC_ATTRIBUTE;
  hdr->header_level		= header_level;
  strcpy (hdr->name, name);
  len = strlen (name);
  hdr->crc			= 0x0000;
  hdr->extend_type		= EXTEND_UNIX;
  hdr->unix_last_modified_stamp	= v_stat->st_mtime;
				/* since 00:00:00 JAN.1.1970 */
#ifdef NOT_COMPATIBLE_MODE
/*  Please need your modification in this space. */
#else
  hdr->unix_mode		= v_stat->st_mode;
#endif

  hdr->unix_uid			= v_stat->st_uid;
  hdr->unix_gid			= v_stat->st_gid;

  if (is_directory (v_stat))
    {
      bcopy (LZHDIRS_METHOD, hdr->method, METHOD_TYPE_STRAGE);
      hdr->attribute = GENERIC_DIRECTORY_ATTRIBUTE;
      hdr->original_size = 0;
      if (len > 0 && hdr->name[len-1] != '/')
		strcpy (&hdr->name[len++], "/");
    }
  if (generic_format)
    unix_to_generic_filename (hdr->name, len);
}

/* Write unix extended header or generic header. */
void
write_header (FILE *nafp, LzHeader *hdr)
{
  int header_size;
  int name_length;
  char data[LZHEADER_STRAGE];
  char *p;

  bzero (data, LZHEADER_STRAGE);
  bcopy (hdr->method, data + I_METHOD, METHOD_TYPE_STRAGE);
  setup_put (data + I_PACKED_SIZE);
  put_longword (hdr->packed_size);
  put_longword (hdr->original_size);

  if (hdr->header_level == HEADER_LEVEL2)
      put_longword ((long)hdr->unix_last_modified_stamp);
  else
      put_longword (hdr->last_modified_stamp);

  switch (hdr->header_level)
    {
    case HEADER_LEVEL0:
      put_byte (hdr->attribute);
      break;
    case HEADER_LEVEL1:
    case HEADER_LEVEL2:
      put_byte (0x20);
      break;
    }

  put_byte (hdr->header_level);

  convdelim(hdr->name, DELIM2);
  if (hdr->header_level != HEADER_LEVEL2)
    {
      if (p = (char *)rindex(hdr->name, DELIM2))
	name_length = strlen(++p);
      else
	name_length = strlen(hdr->name);
      put_byte (name_length);
      bcopy (p ? p : hdr->name, data + I_NAME, name_length);
      setup_put (data + I_NAME + name_length);
    }

  put_word (hdr->crc);
  if (generic_format)
    {
      header_size = I_GENERIC_HEADER_BOTTOM - 2 + name_length;
      data[I_HEADER_SIZE] = header_size;
      data[I_HEADER_CHECKSUM] = calc_sum (data + I_METHOD, header_size);
    }
  else if (header_level == HEADER_LEVEL0)
    {
      /* write old-style extend header */
      put_byte (EXTEND_UNIX);
      put_byte (CURRENT_UNIX_MINOR_VERSION);
      put_longword ((long)hdr->unix_last_modified_stamp);
      put_word (hdr->unix_mode);
      put_word (hdr->unix_uid);
      put_word (hdr->unix_gid);
      header_size = I_UNIX_EXTEND_BOTTOM - 2 + name_length;
      data[I_HEADER_SIZE] = header_size;
      data[I_HEADER_CHECKSUM] = calc_sum (data + I_METHOD, header_size);
    }
  else
    {
      /* write extend header. */
      char *ptr;
      put_byte (EXTEND_UNIX);
      ptr = put_ptr;
      put_word(5);
      if (hdr->header_level == HEADER_LEVEL1)
	header_size = put_ptr - data - 2;
      put_byte(0x50);		/* permission */
      put_word(hdr->unix_mode);
      put_word(7);
      put_byte(0x51);		/* gid and uid */
      put_word(hdr->unix_gid);
      put_word(hdr->unix_uid);
      if (p = (char *)rindex(hdr->name, DELIM2))
	{
	  int i;
	  name_length = p - hdr->name + 1;
	  put_word(name_length + 3);
	  put_byte(2);		/* dirname */
	  for (i = 0; i < name_length; i++)
	    put_byte(hdr->name[i]);
	}
      if (header_level != HEADER_LEVEL2)
	{
	  put_word(7);
	  put_byte(0x54);	/* time stamp */
	  put_longword(hdr->unix_last_modified_stamp);
	  hdr->packed_size += put_ptr - ptr;
	  ptr = put_ptr;
	  setup_put (data + I_PACKED_SIZE);
	  put_longword (hdr->packed_size);
	  put_ptr = ptr;
	  data[I_HEADER_SIZE] = header_size;
	  data[I_HEADER_CHECKSUM] = calc_sum (data + I_METHOD, header_size);
	}
      else
	{
	  int i;
	  if (p = (char *)rindex(hdr->name, DELIM2))
	    name_length = strlen(++p);
	  else
	    {
	      p = hdr->name;
	      name_length = strlen(hdr->name);
	    }
	  put_word(name_length + 3);
	  put_byte(1);		/* filename */
	  for (i = 0; i < name_length; i++)
	    put_byte(*p++);
	}
      header_size = put_ptr - data;
    }
  if (header_level == HEADER_LEVEL2)
    {
      setup_put(data + I_HEADER_SIZE);
      put_word(header_size + 2);
    }
  if (fwrite (data, sizeof (char), header_size + 2, nafp) == 0)
    fatal_error ("Cannot write to temporary file");
  convdelim(hdr->name, DELIM);
}
