/* -*- Mode: C; c-file-style: "gnu" -*- */
/* Handle a .class file embedded in a .zip archive.
   This extracts a member from a .zip file, but does not handle
   uncompression (since that is not needed for classes.zip).

   Copyright (c) 1996 Cygnus Support
   Copyright (C) 1997, 1998 The Hungry Programmers

   See the file "license.terms" for information on usage and redistribution
   of this file, and for a DISCLAIMER OF ALL WARRANTIES.

   Written by Per Bothner <bothner@cygnus.com>, February 1996.
   Seriously hacked by Chris Toshok <toshok@hungry.com>
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "zipfile.h"
#include "zlib.h"
#include "log.h"
#include "compat.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_IO_H
#include <io.h>
#endif
#include <assert.h>

/* XXX hrm...  this seems to crash freebsd with the JDK1.2 .jar files, so
   let's take it out for the time being . */
/* linux doesn't have a problem with this. */
#ifndef linux
#undef HAVE_MMAN_H
#endif

#ifdef HAVE_SYS_MMAN_H
#undef HAVE_SYS_MMAN_H
#endif

#ifdef HAVE_SYS_MMAN_H
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#endif

#define MYLOG "Inflate"

/* This stuff is partly based on the 28 August 1994 public release of the
Info-ZIP group's portable UnZip zipfile-extraction program (and related
utilities). */

/*************/
/*  Defines  */
/*************/

#define UNZIP
#define UNZIP_VERSION     20   /* compatible with PKUNZIP 2.0 */
#define VMS_UNZIP_VERSION 42   /* if OS-needed-to-extract is VMS:  can do */


#define ZSUFX             ".zip"
#define CENTRAL_HDR_SIG   "\113\001\002"   /* the infamous "PK" signature */
#define LOCAL_HDR_SIG     "\113\003\004"   /*  bytes, sans "P" (so unzip */
#define END_CENTRAL_SIG   "\113\005\006"   /*  executable not mistaken for */
#define EXTD_LOCAL_SIG    "\113\007\010"   /*  zipfile itself) */

#define STORED            0    /* compression methods */
#define SHRUNK            1
#define REDUCED1          2
#define REDUCED2          3
#define REDUCED3          4
#define REDUCED4          5
#define IMPLODED          6
#define TOKENIZED         7
#define DEFLATED          8
#define NUM_METHODS       9    /* index of last method + 1 */
/* don't forget to update list_files() appropriately if NUM_METHODS changes */

#define PK_OK             0    /* no error */
#define PK_COOL           0    /* no error */
#define PK_GNARLY         0    /* no error */
#define PK_WARN           1    /* warning error */
#define PK_ERR            2    /* error in zipfile */
#define PK_BADERR         3    /* severe error in zipfile */
#define PK_MEM            4    /* insufficient memory */
#define PK_MEM2           5    /* insufficient memory */
#define PK_MEM3           6    /* insufficient memory */
#define PK_MEM4           7    /* insufficient memory */
#define PK_MEM5           8    /* insufficient memory */
#define PK_NOZIP          9    /* zipfile not found */
#define PK_PARAM          10   /* bad or illegal parameters specified */
#define PK_FIND           11   /* no files found */
#define PK_DISK           50   /* disk full */
#define PK_EOF            51   /* unexpected EOF */

/*---------------------------------------------------------------------------
    True sizes of the various headers, as defined by PKWARE--so it is not
    likely that these will ever change.  But if they do, make sure both these
    defines AND the typedefs below get updated accordingly.
  ---------------------------------------------------------------------------*/
#define LREC_SIZE     26    /* lengths of local file headers, central */
#define CREC_SIZE     42    /*  directory headers, and the end-of-    */
#define ECREC_SIZE    18    /*  central-dir record, respectively      */


#ifndef SEEK_SET
#  define SEEK_SET  0
#endif
#ifndef SEEK_CUR
#  define SEEK_CUR  1
#endif
#ifndef SEEK_END
#  define SEEK_END  2
#endif

/**************/
/*  Typedefs  */
/**************/

typedef char              boolean;
typedef unsigned char     uch;  /* code assumes unsigned bytes; these type-  */
typedef unsigned short    ush;  /*  defs replace byte/UWORD/ULONG (which are */
typedef unsigned int      ulg;  /*  predefined on some systems) & match zip  */

/*---------------------------------------------------------------------------
    Zipfile layout declarations.  If these headers ever change, make sure the
    xxREC_SIZE defines (above) change with them!
  ---------------------------------------------------------------------------*/

typedef uch   local_byte_hdr[ LREC_SIZE ];
#      define L_VERSION_NEEDED_TO_EXTRACT_0     0
#      define L_VERSION_NEEDED_TO_EXTRACT_1     1
#      define L_GENERAL_PURPOSE_BIT_FLAG        2
#      define L_COMPRESSION_METHOD              4
#      define L_LAST_MOD_FILE_TIME              6
#      define L_LAST_MOD_FILE_DATE              8
#      define L_CRC32                           10
#      define L_COMPRESSED_SIZE                 14
#      define L_UNCOMPRESSED_SIZE               18
#      define L_FILENAME_LENGTH                 22
#      define L_EXTRA_FIELD_LENGTH              24

typedef uch   cdir_byte_hdr[ CREC_SIZE ];
#      define C_VERSION_MADE_BY_0               0
#      define C_VERSION_MADE_BY_1               1
#      define C_VERSION_NEEDED_TO_EXTRACT_0     2
#      define C_VERSION_NEEDED_TO_EXTRACT_1     3
#      define C_GENERAL_PURPOSE_BIT_FLAG        4
#      define C_COMPRESSION_METHOD              6
#      define C_LAST_MOD_FILE_TIME              8
#      define C_LAST_MOD_FILE_DATE              10
#      define C_CRC32                           12
#      define C_COMPRESSED_SIZE                 16
#      define C_UNCOMPRESSED_SIZE               20
#      define C_FILENAME_LENGTH                 24
#      define C_EXTRA_FIELD_LENGTH              26
#      define C_FILE_COMMENT_LENGTH             28
#      define C_DISK_NUMBER_START               30
#      define C_INTERNAL_FILE_ATTRIBUTES        32
#      define C_EXTERNAL_FILE_ATTRIBUTES        34
#      define C_RELATIVE_OFFSET_LOCAL_HEADER    38

typedef uch   ec_byte_rec[ ECREC_SIZE+4 ];
/*     define SIGNATURE                         0   space-holder only */
#      define NUMBER_THIS_DISK                  4
#      define NUM_DISK_WITH_START_CENTRAL_DIR   6
#      define NUM_ENTRIES_CENTRL_DIR_THS_DISK   8
#      define TOTAL_ENTRIES_CENTRAL_DIR         10
#      define SIZE_CENTRAL_DIRECTORY            12
#      define OFFSET_START_CENTRAL_DIRECTORY    16
#      define ZIPFILE_COMMENT_LENGTH            20


typedef struct local_file_header {                 /* LOCAL */
  uch version_needed_to_extract[2];
  ush general_purpose_bit_flag;
  ush compression_method;
  ush last_mod_file_time;
  ush last_mod_file_date;
  ulg crc32;
  ulg csize;
  ulg ucsize;
  ush filename_length;
  ush extra_field_length;
} local_file_hdr;

typedef struct central_directory_file_header {     /* CENTRAL */
  uch version_made_by[2];
  uch version_needed_to_extract[2];
  ush general_purpose_bit_flag;
  ush compression_method;
  ush last_mod_file_time;
  ush last_mod_file_date;
  ulg crc32;
  ulg csize;
  ulg ucsize;
  ush filename_length;
  ush extra_field_length;
  ush file_comment_length;
  ush disk_number_start;
  ush internal_file_attributes;
  ulg external_file_attributes;
  ulg relative_offset_local_header;
} cdir_file_hdr;

typedef struct end_central_dir_record {            /* END CENTRAL */
  ush number_this_disk;
  ush num_disk_with_start_central_dir;
  ush num_entries_centrl_dir_ths_disk;
  ush total_entries_central_dir;
  ulg size_central_directory;
  ulg offset_start_central_directory;
  ush zipfile_comment_length;
} ecdir_rec;


/************/
/*  Macros  */
/************/

#ifndef MAX
#  define MAX(a,b)   ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#  define MIN(a,b)   ((a) < (b) ? (a) : (b))
#endif


/***********************/
/* Function makeword() */
/***********************/

static inline ush makeword(uch* b)
{
  /*
   * Convert Intel style 'short' integer to non-Intel non-16-bit
   * host format.  This routine also takes care of byte-ordering.
     */
  return (ush)((b[1] << 8) | b[0]);
}


/***********************/
/* Function makelong() */
/***********************/

static inline ulg makelong(uch* sig)
{
  /*
   * Convert intel style 'long' variable to non-Intel non-16-bit
   * host format.  This routine also takes care of byte-ordering.
     */
  return (((ulg)sig[3]) << 24)
    + (((ulg)sig[2]) << 16)
    + (((ulg)sig[1]) << 8)
    + ((ulg)sig[0]);
}

static int
zip_dir_compare(const void *p1, const void *p2)
{
  ZipDirectory *z1 = *(ZipDirectory**)p1;
  ZipDirectory *z2 = *(ZipDirectory**)p2;

  return strcmp(ZIPDIR_FILENAME(z1), ZIPDIR_FILENAME(z2));
}

int
read_zip_archive (zipf)
     register ZipFile *zipf;
{
  int i;
  int dir_last_pad;
  char *dir_ptr;
#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_UNALIGNEDACCESS)
  char *buffer;
#else
  char buffer[100];
#endif

  zipf->size = lseek (zipf->fd, 0L, SEEK_END);
  if (zipf->size < (ECREC_SIZE+4))
    return -1;

#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_UNALIGNEDACCESS)
  /* Map R/W for now so's we can muck with the directory down below.  */
  zipf->mmap_base = mmap(0, zipf->size, PROT_READ|PROT_WRITE,
			 MAP_PRIVATE, zipf->fd, 0);
  if (zipf->mmap_base == (char *)-1)
    return -1;
  buffer = zipf->mmap_base + zipf->size - (ECREC_SIZE+4);
#else
  if (lseek (zipf->fd, -(ECREC_SIZE+4), SEEK_CUR) <= 0)
    return -1;
  if (read (zipf->fd, buffer, ECREC_SIZE+4) != ECREC_SIZE+4)
    return -2;
#endif

  zipf->count = makeword(&buffer[TOTAL_ENTRIES_CENTRAL_DIR]);
  zipf->dir_size = makelong(&buffer[SIZE_CENTRAL_DIRECTORY]);

#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_UNALIGNEDACCESS)
  /* XXX Is this the same as the malloc down below? [pere] */
  zipf->central_directory = (zipf->mmap_base + zipf->size -
			     (zipf->dir_size + ECREC_SIZE + 4));
#else
  /* Allocate 1 more to allow appending '\0' to last filename. */
  zipf->central_directory = malloc (zipf->dir_size+1);

  if (lseek (zipf->fd, -(zipf->dir_size+ECREC_SIZE+4), SEEK_CUR) < 0)
    return -2;
  if (read (zipf->fd, zipf->central_directory, zipf->dir_size) < 0)
    return -2;
#endif

#ifdef TEST
  printf ("number_this_disk = %d\n", makeword(&buffer[NUMBER_THIS_DISK]));
  printf ("num_disk_with_start_central_dir = %d\n",
	  makeword(&buffer[NUM_DISK_WITH_START_CENTRAL_DIR]));
  printf ("num_entries_centrl_dir_ths_disk = %d\n",
          makeword(&buffer[NUM_ENTRIES_CENTRL_DIR_THS_DISK]));
  printf ("total_entries_central_dir = %d\n",
          makeword(&buffer[TOTAL_ENTRIES_CENTRAL_DIR]));
  printf ("size_central_directory = %d\n",
          makelong(&buffer[SIZE_CENTRAL_DIRECTORY]));
  printf ("offset_start_central_directory = %d\n",
          makelong(&buffer[OFFSET_START_CENTRAL_DIRECTORY]));
  printf ("zipfile_comment_length = %d\n",
          makeword(&buffer[ZIPFILE_COMMENT_LENGTH]));
#endif

  dir_last_pad = 0;
  dir_ptr = zipf->central_directory;
  zipf->sorted_entries = (ZipDirectory**)malloc(zipf->count * sizeof(ZipDirectory*));
  for (i = 0; i < zipf->count; i++)
    {
      ZipDirectory *zipd = (ZipDirectory*)(dir_ptr + dir_last_pad);
      int compression_method = makeword (&dir_ptr[4+C_COMPRESSION_METHOD]);
      long compressed_size = makelong (&dir_ptr[4+C_COMPRESSED_SIZE]);
      long uncompressed_size = makelong (&dir_ptr[4+C_UNCOMPRESSED_SIZE]);
      long filename_length = makeword (&dir_ptr[4+C_FILENAME_LENGTH]);
      long extra_field_length = makeword (&dir_ptr[4+C_EXTRA_FIELD_LENGTH]);
      long file_comment_length = makeword (&dir_ptr[4+C_FILE_COMMENT_LENGTH]);
      long record_length;
      int unpadded_direntry_length;
      int zip_version = (unsigned char)dir_ptr[4+C_VERSION_MADE_BY_0];
      
      record_length = filename_length+extra_field_length+file_comment_length;

      if ((dir_ptr-zipf->central_directory)+record_length+CREC_SIZE+4
	  > zipf->dir_size)
	return -1;

      zipf->sorted_entries[i] = zipd;

      zipd->filename_length = filename_length;
      zipd->compression_method = compression_method;
      zipd->uncompressed_size = uncompressed_size;
      zipd->compressed_size = compressed_size;
      zipd->zip_version = zip_version;
      /*
	#if defined(__GNUC__)
	#define DIR_ALIGN __alignof__(ZipDirectory)
	#else*/
#define DIR_ALIGN sizeof(long)
      /*
	#endif
      */
      zipd->filestart = makelong (&dir_ptr[4+C_RELATIVE_OFFSET_LOCAL_HEADER])
	+ (LREC_SIZE+4) + record_length;
      /* This is definately a hack - but it fixes things for now */
      if (record_length != filename_length) {
	zipd->filestart += 4;
      }
      zipd->filename_offset = CREC_SIZE+4 - dir_last_pad;
      unpadded_direntry_length = zipd->filename_offset + record_length;
      zipd->direntry_size =
	((unpadded_direntry_length + DIR_ALIGN) / DIR_ALIGN) * DIR_ALIGN;
      dir_last_pad = zipd->direntry_size - unpadded_direntry_length;

      dir_ptr[4+CREC_SIZE+filename_length] = 0;
      dir_ptr = (char*)zipd + unpadded_direntry_length;
    }

#ifdef HAVE_MERGESORT
  mergesort(zipf->sorted_entries, zipf->count, sizeof(ZipDirectory*),
	    zip_dir_compare);
#elif defined(HAVE_QSORT)
  qsort(zipf->sorted_entries, zipf->count, sizeof(ZipDirectory*),
	zip_dir_compare);
#else
#error um... i need to sort stuff here.
#endif

#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_UNALIGNEDACCESS)
  /* Now that we're done mucking, set the whole thing back read-only.  */
  i = zipf->size;
#if defined(HAVE_GETPAGESIZE)
  /* Round size up to page size - this does no harm on any system and
   * avoids a bug on BSDIs.
   */
  i = (i + getpagesize() - 1) & -getpagesize();
#endif
  mprotect(zipf->mmap_base, i, PROT_READ);
#endif

  return 0;
}

#define WSIZE 0x8000
/* this next function blatantly taken and munged into a much smaller,
   more specialized form from the unzip source by toshok@hungry.com,
   Mar 3, 1998.  Here's the message from the top of the unzip source
   file: */

/* inflate.c -- put in the public domain by Mark Adler
   version c15c, 28 March 1997 */
static int
inflate_entry(char *output_buffer,
	      int output_length,
	      ZipFile *zipf,
	      ZipDirectory *zipd)
{
  int input_length = zipd->compressed_size;
  char *input_buffer = malloc(input_length);
  int i, windowBits, err=Z_OK;
  int wsize = WSIZE;
  z_stream zstrm;

  assert(NULL != input_buffer); /* XXX Should probably throw exception instead */

  lseek(zipf->fd, zipd->filestart, SEEK_SET);
  read(zipf->fd, input_buffer, input_length);

  /* GRR:  "U" may not be compatible with K&R compilers */
  if (wsize < 256U)   /* window sizes 2^8 .. 2^15 allowed currently */
    return 2;
  
  /* windowBits = log2(wsize) */
  for (i = wsize, windowBits = 0;  !(i & 1);  i >>= 1, ++windowBits);
  
  zstrm.next_out = output_buffer;
  zstrm.avail_out = output_length; /*wsize;*/
  
  zstrm.next_in = input_buffer;
  zstrm.avail_in = input_length;
  
  zstrm.zalloc = (alloc_func)Z_NULL;
  zstrm.zfree = (free_func)Z_NULL;
  
  JAVARLOG0(MYLOG, 2, "initializing inflate()\n");
  err = inflateInit2(&zstrm, -windowBits);
  
  if (err == Z_MEM_ERROR)
    return 3;
  else if (err != Z_OK)
    JAVARLOG1(MYLOG, 2, "oops!  (inflateInit2() err = %d)\n", err);
  
  while (zstrm.avail_in > 0) {
    while (zstrm.avail_out > 0) {
      err = inflate(&zstrm, Z_PARTIAL_FLUSH);
      
      if (err == Z_DATA_ERROR)
	return 2;
      else if (err == Z_MEM_ERROR)
	return 3;
      else if (err != Z_OK && err != Z_STREAM_END)
	JAVARLOG1(MYLOG, 2, "oops!  (inflate(first loop) err = %d)\n", err);
      
      if (zstrm.avail_in <= 0L)          /* "END-of-entry-condition" ? */
	break;
      
      JAVARLOG1(MYLOG, 2, "     avail_in = %d\n", zstrm.avail_in);
    }
  }
  
  /* no more input, so loop until we have all output */
  JAVARLOG1(MYLOG, 2, "beginning final loop:  err = %d\n", err);
  while (err != Z_STREAM_END) {
    err = inflate(&zstrm, Z_PARTIAL_FLUSH);
    if (err == Z_DATA_ERROR)
      return 2;
    else if (err == Z_MEM_ERROR)
      return 3;
    else if (err == Z_BUF_ERROR) {              /* DEBUG */
      JAVARLOG0(MYLOG, 2, "zlib inflate() did not detect stream end\n");
      break;
    } else if (err != Z_OK && err != Z_STREAM_END) {
      JAVARLOG1(MYLOG, 2, "oops!  (inflate(final loop) err = %d)\n", err);
      return 3;
    }
    JAVARLOG1(MYLOG, 2, "final loop:  flushing %ld bytes\n",
	      (long)(wsize - zstrm.avail_out));
  }
  JAVARLOG2(MYLOG, 2, "total in = %ld, total out = %ld\n", zstrm.total_in,
	    zstrm.total_out);
  
  err = inflateReset(&zstrm);
  if (err != Z_OK)
    JAVARLOG1(MYLOG, 2, "oops!  (inflateReset() err = %d)\n", err);

  free(input_buffer);

  return Z_OK;
}

void
free_zipfile_entry(ZipDirectory *zipd,
		   char *buf,
		   int length,
		   int malloced)
{
  if (malloced) 
    free(buf);
#if defined(HAVE_SYS_MMAN_H)
  else
    {
      /* XXX These +4 must also change like the one in get_zipfile_entry */
      int offset;
      int difference;
      
      offset = zipd->filestart + 4 - ((zipd->filestart + 4) % 4096);
      
      difference = zipd->filestart + 4 - offset;
      
      munmap(buf - difference,
	     zipd->uncompressed_size + difference);
    }
#endif
}

int
get_zipfile_entry(ZipFile *zipf,
		  ZipDirectory *zipd,
		  char **buf,
		  int *length,
		  int *malloced)
{
  int strange_skip_value = 0;
  *malloced = 0;
  *length = zipd->uncompressed_size;
  JAVARLOG1(MYLOG, 1, "Zip entry v=0x%x\n", zipd->zip_version);

  /*
   * XXX This is an ugly hack,  I don't know what I am doing, nor do I know
   * XXX the .zip file format.  No idea if the version and the +4 is
   * XXX related, but it was on my system. :-) [pere 1998-05-14]
   */
  if (0x15 < zipd->zip_version)
    strange_skip_value = 4;

  if (zipd->compression_method == STORED)
    {
      /* the easy one. */
#if defined(HAVE_SYS_MMAN_H)
      int offset;
      int difference;

      offset = zipd->filestart + strange_skip_value
	- ((zipd->filestart + strange_skip_value) % 4096);

      difference = zipd->filestart + strange_skip_value - offset;

      *buf = mmap(0, *length + difference, PROT_READ,
		  MAP_PRIVATE, zipf->fd,
		  offset);
      if (*buf != MAP_FAILED)
	{
	  *buf += difference;

	  return Z_OK;
	}

      perror("mmap");

      /* fall through to the malloc case. */
#endif
      *malloced = 1;
      *buf = (char*)malloc(*length);
      assert(NULL != *buf);
      lseek(zipf->fd, zipd->filestart + strange_skip_value, SEEK_SET);
      read(zipf->fd, *buf, *length);
      return Z_OK;
    }
  else if (zipd->compression_method == DEFLATED)
    {
      *malloced = 1;
      *buf = (char*)malloc(*length);
      assert(NULL != *buf);

      return (inflate_entry(*buf, *length, zipf, zipd) == Z_OK ? 0 : -1);
    }
  else
    {
#ifdef DEBUG
      fprintf (stderr, "unhandled compression type %d, skipping zip entry\n", zipd->compression_method);
#endif
      return -1;
    }
}
