/**************************************************************************
* Copyright  (c) 2001 by Acunia N.V. All rights reserved.                 *
*                                                                         *
* This software is copyrighted by and is the sole property of Acunia N.V. *
* and its licensors, if any. All rights, title, ownership, or other       *
* interests in the software remain the property of Acunia N.V. and its    *
* licensors, if any.                                                      *
*                                                                         *
* This software may only be used in accordance with the corresponding     *
* license agreement. Any unauthorized use, duplication, transmission,     *
*  distribution or disclosure of this software is expressly forbidden.    *
*                                                                         *
* This Copyright notice may not be removed or modified without prior      *
* written consent of Acunia N.V.                                          *
*                                                                         *
* Acunia N.V. reserves the right to modify this software without notice.  *
*                                                                         *
*   Acunia N.V.                                                           *
*   Vanden Tymplestraat 35      info@acunia.com                           *
*   3000 Leuven                 http://www.acunia.com                     *
*   Belgium - EUROPE                                                      *
**************************************************************************/

/*
** $Id: zip.c,v 1.7 2001/09/06 18:59:41 buytaert Exp $
*/

#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#include "jamjar.h"
#include "zip.h"
#include "resource.h"
#include "argument.h"

/*
** Stuff to read from a stream of bytes. After reading, set the byte data pointer to the
** next position to parse. The stream is a memory block of unsigned chars.
*/

#define bytes2short(s)                  (short)((s[1] << 8) | s[0])
#define bytes2word(s)                   (unsigned int)((s[3] << 24) | (s[2] << 16) | (s[1] << 8) | s[0])
#define readWord(s)                     bytes2word(s); (s) += sizeof(unsigned int)
#define readShort(s)                    bytes2short(s); (s) += sizeof(unsigned short)
#define readByte(s)                     (*(s)); (s) += sizeof(unsigned char)

/*
** Stuff to write to a file descriptor.
*/

static void writeWord(int fd, unsigned int w) {

  unsigned char word[4];
  unsigned char *wp = (unsigned char *)&w;

#ifdef LITTLE_ENDIAN
  word[3] = wp[3];
  word[2] = wp[2];
  word[1] = wp[1];
  word[0] = wp[0];
#else
  word[3] = wp[0];
  word[2] = wp[1];
  word[1] = wp[2];
  word[0] = wp[3];
#endif

  write(fd, word, sizeof(unsigned int));
  
}

static void writeShort(int fd, unsigned short s) {

  unsigned char shrt[2];
  unsigned char *sp = (unsigned char *)&s;

#ifdef LITTLE_ENDIAN
  shrt[1] = sp[1];
  shrt[0] = sp[0];
#else
  shrt[1] = sp[0];
  shrt[0] = sp[1];
#endif

  write(fd, shrt, sizeof(unsigned short));
  
}

void entry_free(j_entry e) {

}

static unsigned char * entry_read(j_zip zip, j_entry list, unsigned char * stream, int mode) {

  j_entry entry;
  size_t n_size;
  size_t c_size;
  size_t u_size;
  size_t e_size;
  j_crc crc;
  j_res res;
  j_res found;
  j_dosdate date;
  j_dostime time;
  int keep = 1;
  
  entry = jj_calloc(1, sizeof(j_Entry));
  entry->zip = zip;

  stream += sizeof(unsigned int);    // signature
  stream += sizeof(unsigned short);  // version
  stream += sizeof(unsigned short);  // flags
  stream += sizeof(unsigned short);  // compression 
  time = readShort(stream);          // mod time
  date = readShort(stream);          // mod date
  crc = readWord(stream);            // crc
  c_size = readWord(stream);         // compressed size
  u_size = readWord(stream);         // uncompressed size
  n_size = readShort(stream);        // length of name
  e_size = readShort(stream);        // extra data size

  res = res_entry(stream, n_size);
  stream += n_size;

  if (mode == OUTPUT_JAR) {
    found = res_exists(res);
    if (found) {
      res_free(res);
      keep = 0;
      res = found;
    }
    else {
      entry->res = res;
      res->entry = entry;
    }
  }

  if (keep) {
    stream += e_size;
    res->c_data = jj_calloc(c_size, sizeof(unsigned char));
    memcpy(res->c_data, stream, c_size);
    stream += c_size;
    res->c_size = c_size;
    res->u_size = u_size;
    res->crc = crc;
    res->time = dos2unix(date, time);
    list_insert(list, entry);
    zip->num_entries += 1;

    /*
    ** If mode is OUTPUT_JAR, we just set the flag RES_WRITE. The resource is in the
    ** hashtable allready. For LIST_JAR, we don't put the resources allocated in the hastable. We
    ** use the chains of the entries themselves. If it concerns the manifest file and we have one allready
    ** in the options, meaning one was passed on the command line, we unset the RES_WRITE flag again.
    */

    if (mode == OUTPUT_JAR) {
      setFlag(res->flags, RES_WRITE);
      if (strcmp(res->z_name, "META-INF/MANIFEST.MF") == 0) {
        zip->manifest = res;
        if (options->manifest) {
          printf("XXXXXXXXX Using '%s' as passed manifest file.\n", options->manifest->file);
          unsetFlag(res->flags, RES_WRITE);
        }
      }
      else if (strcmp(res->z_name, "META-INF/INDEX.LIST") == 0) {
        zip->index = res;
      }
      else {
        res_add(res);
      }
    }
    else {
      entry->res = res;
      res->entry = entry;
    }
  }
  else {
    stream += e_size;        // Skip extra data
    stream += c_size;        // Skip the compressed data
    entry_free(entry);
  }

  return stream;
  
}

/*
** Read a zip, mode determines which way we need to manipulate the data.
**
** a) mode == OUTPUT_JAR
**    Means that we are reading the output jar file. We only add entries
**    that are not yet found back in the resource hashtable.
**
** b) mode == LIST_JAR
**    Means we are reading the zip for listing it afterwards. Don't read the data, only
**    the name, date, time and sizes.
*/

j_zip zip_read(j_arg arg, int mode) {

  unsigned char * stream;
  unsigned char * buffer;
  size_t stream_len;
  int fd;
  size_t nread;
  unsigned int sig;
  j_zip zip;
  j_Entry List;
  j_entry list = &List;
  
  stream_len = arg->size;
  stream = jj_calloc(stream_len, sizeof(unsigned char));
  buffer = stream;
  if (stream == NULL) {
    perror("allocing zip stream");
    exit(1);
  }
  
  fd = open(arg->file, O_RDONLY);
  if (fd == -1) {
    perror("opening zip");
    jj_free(buffer);
    exit(1);
  }
  
  nread = read(fd, stream, stream_len);
  if (nread == -1) {
    perror("reading from zip");
    jj_free(buffer);
    close(fd);
    exit(1);
  }
  
  if (nread != stream_len) {
    fprintf(stderr, "premature end.\n");
    jj_free(buffer);
    close(fd);
    exit(1);
  }

  logmsg(2, "read %d bytes from '%s'\n", stream_len, arg->file);

  zip = jj_calloc(1, sizeof(j_Zip));
  zip->arg = arg;
  list_init(list);

  sig = readWord(stream);
  while (sig == Z_LOCAL_SIG) {
    stream -= sizeof(unsigned int);
    stream = entry_read(zip, list, stream, mode);
    sig = readWord(stream);
  }
  
  if (sig != 0x02014b50) {
    jj_free(buffer);
    close(fd);
    // free entries also
    fprintf(stderr, "bad signature 0x%08x in file '%s'\n", sig, arg->file);
    exit(1);
  }

  jj_free(buffer);
  close(fd);

  if (list->next != list) {
    list_break(list, zip->entries);
  }

  return zip;
  
}

size_t res_write_local(j_res res) {

  size_t size;
  int fd = options->out;
  j_dosdate date;
  j_dostime time;

  unix2dos(res->time, &date, &time);
  
  writeWord(fd, Z_LOCAL_SIG);          //  4 local signature
  writeShort(fd, ZIP_VERSION);         //  6 version needed to extract
  writeShort(fd, ZIP_FLAGS);           //  8 general purpose bit flags
  if (res->c_size == res->u_size) {
    writeShort(fd, ZIP_STORE);         // 10 file is only stored
  }
  else {
    writeShort(fd, ZIP_DEFLATE);       // 10 deflate compression method
  }
  writeShort(fd, time);                // 12 last modification time
  writeShort(fd, date);                // 14 last modification date
  writeWord(fd, res->crc);             // 18 crc value
  writeWord(fd, res->c_size);          // 22 compressed size
  writeWord(fd, res->u_size);          // 26 uncompressed size
  writeShort(fd, strlen(res->z_name)); // 28 filename length
  writeShort(fd, 4);                   // 30 extra field length
  size = 30;

  size += write(fd, res->z_name, strlen(res->z_name));

  writeShort(fd, 0x7856);              // Extra header ID, i.e. our own jamjar ID
  writeShort(fd, 0x0000);              // Extra data size, i.e. 0
  size += 4;
  
  return size;

}

size_t res_write_central(j_res res) {

  size_t size;
  int fd = options->out;
  j_dosdate date;
  j_dostime time;

  unix2dos(res->time, &date, &time);
  
  writeWord(fd, Z_DIR_SIG);            //  4
  writeShort(fd, Z_UNIX_MADE);         //  6
  writeShort(fd, ZIP_VERSION);         //  8
  writeShort(fd, ZIP_FLAGS);           // 10
  if (res->c_size == res->u_size) {
    writeShort(fd, ZIP_STORE);         // 12 file is only stored
  }
  else {
    writeShort(fd, ZIP_DEFLATE);       // 12 deflate compression method
  }
  writeShort(fd, time);                // 14
  writeShort(fd, date);                // 16
  writeWord(fd, res->crc);             // 20
  writeWord(fd, res->c_size);          // 24
  writeWord(fd, res->u_size);          // 28
  writeShort(fd, strlen(res->z_name)); // 30
  writeShort(fd, 4);                   // 32
  writeShort(fd, 0);                   // 34 File comment length
  writeShort(fd, 0);                   // 36 Disk number start
  writeShort(fd, 0);                   // 38 Internal file attributes
  writeWord(fd, 0);                    // 42 External file attributes
  writeWord(fd, res->offset);          // 46
  size = 46;
    
  size += write(fd, res->z_name, strlen(res->z_name));
  
  writeShort(fd, 0x7856);              // Extra header ID, i.e. our own jamjar ID
  writeShort(fd, 0x0000);              // Extra data size, i.e. 0
  size += 4;

  return size;

}

void zip_write_trailer(j_out out) {

  int fd = options->out;
  
  writeWord(fd, Z_DIR_END_SIG);        //  4
  writeShort(fd, 0);                   //  6 Number of this disk
  writeShort(fd, 0);                   //  8 Number of disk with start of central directory
  writeShort(fd, out->count);          // 10
  writeShort(fd, out->count);          // 12
  writeWord(fd, out->dir_size);        // 16 Size of this central directory
  writeWord(fd, out->offset);          // 20 Offset of the start of the central directory
  writeShort(fd, 0);                   // 22 Zip file comment length

}

/*
** Determine if a zip contains compressed entries yes or no. We stop as soon as we find out,
** i.e. if there is at least a single entry that has been compressed, we assume that the whole
** zip is compressed. If the file has been 'stream' compressed, the compressed and uncompressed
** size in the local header are both 0 and this means that we end up misinterpreting the file.
** This is not a big problem, it just means that we assume that the file contains no compressed
** entries and we will compress it. If jamjar is used consistently in a project, there will be
** no stream compressed files anyhow. This should only be called for jarfiles that need to be
** included in the output jar, not on the output jar itself.
*/

static unsigned char * entry_is_compressed(unsigned char * stream, int * compressed) {

  size_t c_size;
  size_t u_size;
  size_t n_size;
  size_t e_size;
  
  stream += sizeof(unsigned int);    // signature
  stream += sizeof(unsigned short);  // version
  stream += sizeof(unsigned short);  // flags
  stream += sizeof(unsigned short);  // compression 
  stream += sizeof(unsigned short);  // mod time
  stream += sizeof(unsigned short);  // mod date
  stream += sizeof(unsigned int);    // crc
  c_size = readWord(stream);         // compressed size
  u_size = readWord(stream);         // uncompressed size
  n_size = readShort(stream);        // length of name
  e_size = readShort(stream);        // extra data size

  stream += n_size;
  stream += e_size;
  stream += c_size;

  *compressed = (c_size < u_size);

  return stream;
  
}

int zip_is_compressed(j_byte * data) {

  unsigned char * stream;
  unsigned int sig;
  int compressed = 0;
  
  stream = data;

  sig = readWord(stream);
  while (sig == Z_LOCAL_SIG && compressed == 0) {
    stream -= sizeof(unsigned int);
    stream = entry_is_compressed(stream, &compressed);
    sig = readWord(stream);
  }
  
  return compressed;
  
}
