/*              LHarc Dearchiver Main                                 */

/* Opens an archive and tests each internal file to see if it matches */
/* a specified wildcard pattern. If it does, it dearchives 1 buffers  */
/* worth on each call. It does this for every file in the archive.    */
/* 0 <= x != buffer size -> end of file                               */
/* 0 <= x <= buffer size -> x = # of bytes read into buf              */
/*      x >  buffer size -> # of bytes read into buffer = buf size    */
/* 0 >  x                -> end of archive                            */
/* if no files match pattern, 0 is returned. Next call -1 is returned */

#include "lharc.h"

int     NotInterruptedCall = 1;
int     ForceReturn;
extern  long WinSiz, BCount, TotRd;
char    expanded_archive_name[FILENAME_LENGTH];
char    *writting_filename;
char    *archive_name;
extern  char *WildLZH;
extern  long BufrCnt;
long    TotSiz;
long    cursize;
long    HWinSiz;
char    *BfrPtr;
LzHeader hdr;
long    pos;
FILE    *afp=NULL;

long
FillBufWithLZH( reading_filename, buffer )
  char    *reading_filename;
  char    *buffer;
{
  BfrPtr = buffer;
  HWinSiz = WinSiz >> 1;
  archive_name = reading_filename;
  ForceReturn = 0;
  if( NotInterruptedCall ) {
     if( afp == NULL ) {
        /* open archive file */
        if ((afp = open_old_archive ()) == NULL) fatal_error (archive_name);
     }
     if( get_header (afp, &hdr)) {
        pos = ftell( afp);
        BCount = HWinSiz;           /* lh5 only */
        TotRd = hdr.packed_size;    /* lh5 only */
        cursize = hdr.original_size; /* lh5 only */
        TotSiz = 0;
        BufrCnt = 0;
        extract_one( afp, &hdr);
     }
     else {
        /* close archive file */
        fclose( afp);
        afp = NULL;
        return(-1);
     }
  }
  else {
     extract_one (afp, &hdr);
  }

  if( ForceReturn ) {
     NotInterruptedCall = 0;
  }
  else {
     fseek( afp, pos + hdr.packed_size, SEEK_SET);
     if( TotSiz == WinSiz) TotSiz++; /* signify endof int file if nonobvious */
  }

  return(TotSiz);
}

static
message_1 (title, subject, name)
     char *title, *subject, *name;
{
  fprintf (stderr, "LHarc: %s%s ", title, subject);
  fflush (stderr);

  if (errno == 0)
    fprintf (stderr, "%s\n", name);
  else
    perror (name);
}

void
message (subject, name)
     char *subject, *name;
{
  message_1 ("", subject, name);
}

void
warning (subject, name)
     char *subject, *name;
{
  message_1 ("Warning: ", subject, name);
}

void
error (subject, msg)
     char *subject, *msg;
{
  message_1 ("Error: ", subject, msg);
}

void
fatal_error (msg)
     char *msg;
{
  message_1 ("Fatal error:", "", msg);
  ErrP("terminating...");
}

read_error ()
{
  fatal_error (archive_name);
}

/*----------------------------------------------------------------------*/
/*                                                                      */
/*----------------------------------------------------------------------*/

static boolean
expand_archive_name (dst, src)
     char *dst, *src;
{
  register char *p, *dot;

  strcpy (dst, src);

  for (p = dst, dot = (char*)0; ; p ++, dot = (char*)0)
    {
      while (*p == '.')         /* skip leading dots of base name */
        p ++;
      for (; *p != '/'; p ++)
        {
          if (*p == '.')
            dot = p;
          else if (*p == '\0')
            goto _Done;
        }
    }
  _Done:

  if (dot)
    p = dot;

  strcpy (p, ARCHIVENAME_EXTENTION); /* ".lzh" */
  return (strcmp (dst, src) != 0);
}

/*----------------------------------------------------------------------*/
/*                                                                      */
/*----------------------------------------------------------------------*/

static int
calc_sum (p, len)
     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))

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

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

static long
get_longword ()
{
  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
msdos_to_unix_filename (name, len)
     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
generic_to_unix_filename (name, len)
     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
macos_to_unix_filename (name, len)
     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] = ':';
    }
}

/*----------------------------------------------------------------------*/
/*                                                                      */
/*      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 ->|               */
/*                                                                      */
/*----------------------------------------------------------------------*/


static long
gettz ()
{
   return(time((time_t *)NULL));
}

static time_t
generic_to_unix_stamp (t)
     long t;
{
  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;

  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 */

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

  /* LONGTIME is now the time in seconds, since 1970/01/01 00:00:00.  */
  return (time_t)longtime;
}

/*----------------------------------------------------------------------*/
/*                                                                      */
/*----------------------------------------------------------------------*/

boolean
get_header (fp, hdr)
     FILE *fp;
     register LzHeader *hdr;
{
  int header_size;
  int name_length;
  char data[LZHEADER_STRAGE];
  int checksum;
  int i;

  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_CHECKSUM);
  checksum = calc_sum (data + I_METHOD, header_size);
  if (get_byte () != checksum)
    warning ("Checksum error (LHarc file?)", "");

  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_word ();
  name_length           = get_byte ();
  for (i = 0; i < name_length; i ++)
    hdr->name[i] =(char)get_byte ();
  hdr->name[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->minor_version                = 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;
    }

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

    case EXTEND_UNIX:
      hdr->unix_last_modified_stamp     = (time_t)get_longword ();
      hdr->unix_mode                    = get_word ();
      hdr->unix_uid                     = get_word ();
      hdr->unix_gid                     = get_word ();
      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_unix_filename (hdr->name, name_length);
      hdr->unix_last_modified_stamp     =
        generic_to_unix_stamp (hdr->last_modified_stamp);
    }

  return TRUE;
}

/*----------------------------------------------------------------------*/
/*                                                                      */
/*----------------------------------------------------------------------*/

static void
modify_filename_extention (buffer, ext)
     char *buffer;
     char *ext;
{
  register char *p, *dot;

  for (p = buffer, dot = (char*)0; *p; p ++)
    {
      if (*p == '.')
        dot = p;
      else if (*p == '/')
        dot = (char*)0;
    }

  if (dot)
    p = dot;

  strcpy (p, ext);
}

/*----------------------------------------------------------------------*/
/*                                                                      */
/*----------------------------------------------------------------------*/

static boolean
open_old_archive_1 (name, v_fp)
     char *name;
     FILE **v_fp;
{
  FILE *fp;
  struct stat stbuf;

  if (stat (name, &stbuf) >= 0 &&
      (fp = fopen (name, READ_BINARY)) != NULL)
    {
      *v_fp = fp;
      return TRUE;
    }

  *v_fp = NULL;
  return FALSE;
}

FILE *
open_old_archive ()
{
  FILE *fp;

  if (!open_old_archive_1 (archive_name, &fp))
    {
      if (expand_archive_name (expanded_archive_name, archive_name))
        {
          archive_name = expanded_archive_name;
          if (open_old_archive_1 (archive_name, &fp))
            errno = 0;
        }
    }
  return fp;
}
