Logo Search packages:      
Sourcecode: jclassinfo version File versions  Download package

jar.c

/* libjclass - Library for reading java class files
 * Copyright (C) 2003  Nicos Panayides
 *
 * 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 FITNESS 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: jar.c,v 1.9 2004/03/21 05:04:10 anarxia Exp $
 */

/* This file is a modified version of a MAME source file and it has
 * been relicensed under the GPL for libjclass after permission from
 * the original author (Andrea Mazzoleni).
 *
 * Changes from original file:
 * - Use of integer types from inttypes.h instead of the MAME types.
 * - Style changes to follow coding standards of the rest of the sources.
 * - Removed unnecessary functions.
 * - Remove all uses of mame OSD printing functions.
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#include <jclass/jar.h>

static int readcompresszip(JarFile*, const JarEntry*, char*);
static int seekcompresszip (JarFile*, const JarEntry*);
static uint16_t read_word (char*);
static uint32_t read_dword (char*);
static int ecd_find_sig (char*, int, int*);
static int ecd_read (JarFile*);

#define INFLATE_INPUT_BUFFER_MAX 16384
#ifndef MIN
#define MIN(x,y) ((x)<(y)?(x):(y))
#endif

/* -------------------------------------------------------------------------
   Unzip support
 ------------------------------------------------------------------------- */

/* Use these to avoid structure padding and byte-ordering problems */
static uint16_t read_word (char *buf)
{
      uint8_t *ubuf = (uint8_t *) buf;

      return ((uint16_t) ubuf[1] << 8) | (uint16_t) ubuf[0];
}

/* Use these to avoid structure padding and byte-ordering problems */
static uint32_t read_dword (char *buf)
{
      uint8_t *ubuf = (uint8_t *) buf;

      return ((uint32_t) ubuf[3] << 24) | ((uint32_t) ubuf[2] << 16) |
            ((uint32_t) ubuf[1] << 8) | (uint32_t) ubuf[0];
}

/* Locate end-of-central-dir sig in buffer and return offset
   out:
      *offset offset of cent dir start in buffer
   @return
      0 not found
      1 found, *offset valid
*/
static int ecd_find_sig (char *buffer, int buflen, int *offset)
{
      static char ecdsig[] = { 'P', 'K', 0x05, 0x06 };
      int i;
      for (i = buflen - 22; i >= 0; i--)
      {
            if (memcmp (buffer + i, ecdsig, 4) == 0)
            {
                  *offset = i;
                  return 1;
            }
      }
      return 0;
}

/* Read ecd data in zip structure
   in: zip zip->fp, zip->length zip file
   out:
     zip->ecd, zip->ecd_length ecd data
*/
static int ecd_read (JarFile *zip)
{
      char *buf;
      int buf_length = 1024;  /* initial buffer length */
      int offset;

      for (;;)
      {
            if (buf_length > zip->length)
                  buf_length = zip->length;

            if (fseek (zip->fp, zip->length - buf_length, SEEK_SET) != 0)
                  return -1;

            /* allocate buffer */
            buf = (char *) malloc (buf_length);
            if (buf == NULL)
                  return -1;

            if (fread (buf, buf_length, 1, zip->fp) != 1)
            {
                  free (buf);
                  return -1;
            }

            if (ecd_find_sig (buf, buf_length, &offset))
            {
                  zip->ecd_length = buf_length - offset;

                  zip->ecd = (char *) malloc (zip->ecd_length);
                  if (zip->ecd == NULL)
                  {
                        free (buf);
                        return -1;
                  }

                  memcpy (zip->ecd, buf + offset, zip->ecd_length);

                  free (buf);
                  return 0;
            }

            free (buf);
            /* double buffer */
            if (buf_length < zip->length)
                  buf_length *= 2;
            else
                  return -1;
      }
}

/* offsets in end of central directory structure */
#define ZIPESIG         0x00
#define ZIPEDSK         0x04
#define ZIPECEN         0x06
#define ZIPENUM   0x08
#define ZIPECENN  0x0a
#define ZIPECSZ         0x0c
#define ZIPEOFST  0x10
#define ZIPECOML  0x14
#define ZIPECOM   0x16

/* offsets in central directory entry structure */
#define ZIPCENSIG 0x0
#define ZIPCVER         0x4
#define ZIPCOS          0x5
#define ZIPCVXT         0x6
#define ZIPCEXOS  0x7
#define ZIPCFLG         0x8
#define ZIPCMTHD  0xa
#define ZIPCTIM         0xc
#define ZIPCDAT         0xe
#define ZIPCCRC         0x10
#define ZIPCSIZ         0x14
#define ZIPCUNC         0x18
#define ZIPCFNL         0x1c
#define ZIPCXTL         0x1e
#define ZIPCCML         0x20
#define ZIPDSK          0x22
#define ZIPINT          0x24
#define ZIPEXT          0x26
#define ZIPOFST         0x2a
#define ZIPCFN          0x2e

/* offsets in local file header structure */
#define ZIPLOCSIG 0x00
#define ZIPVER          0x04
#define ZIPGENFLG 0x06
#define ZIPMTHD   0x08
#define ZIPTIME         0x0a
#define ZIPDATE         0x0c
#define ZIPCRC          0x0e
#define ZIPSIZE         0x12
#define ZIPUNCMP  0x16
#define ZIPFNLN         0x1a
#define ZIPXTRALN 0x1c
#define ZIPNAME         0x1e

/**
* jclass_jar_open
* @filename: The filename for the jar file.
*
* Opens a jar stream for reading.
*
* Returns: A newly allocated jar stream on success,
* or NULL if any error occured.
*/
JarFile* jclass_jar_open(const char *zipfile)
{
      /* allocate */
      JarFile *zip = (JarFile *) malloc (sizeof (JarFile));

      /* open */
      zip->fp = fopen (zipfile, "rb");
      if (zip->fp == NULL)
      {
            free (zip);
            return NULL;
      }

      /* go to end */
      if (fseek (zip->fp, 0L, SEEK_END) != 0)
      {
            fclose (zip->fp);
            free (zip);
            return NULL;
      }

      /* get length */
      zip->length = ftell (zip->fp);
      if (zip->length <= 0)
      {
            fclose (zip->fp);
            free (zip);
            return NULL;
      }

      /* read ecd data */
      if (ecd_read (zip) != 0)
      {
            fclose (zip->fp);
            free (zip);
            return NULL;
      }

      /* compile ecd info */
      zip->end_of_cent_dir_sig = read_dword (zip->ecd + ZIPESIG);
      zip->number_of_this_disk = read_word (zip->ecd + ZIPEDSK);
      zip->number_of_disk_start_cent_dir = read_word (zip->ecd + ZIPECEN);
      zip->total_entries_cent_dir_this_disk =
            read_word (zip->ecd + ZIPENUM);
      zip->total_entries_cent_dir = read_word (zip->ecd + ZIPECENN);
      zip->size_of_cent_dir = read_dword (zip->ecd + ZIPECSZ);
      zip->offset_to_start_of_cent_dir = read_dword (zip->ecd + ZIPEOFST);

      /* verify that we can work with this zipfile (no disk spanning allowed) */
      if ((zip->number_of_this_disk != zip->number_of_disk_start_cent_dir)
          || (zip->total_entries_cent_dir_this_disk !=
            zip->total_entries_cent_dir)
          || (zip->total_entries_cent_dir < 1))
      {
            free (zip->ecd);
            fclose (zip->fp);
            free (zip);
            return NULL;
      }

      if (fseek (zip->fp, zip->offset_to_start_of_cent_dir, SEEK_SET) != 0)
      {
            free (zip->ecd);
            fclose (zip->fp);
            free (zip);
            return NULL;
      }

      /* read from start of central directory */
      zip->cd = (char *) malloc (zip->size_of_cent_dir);
      if (zip->cd == NULL)
      {
            free (zip->ecd);
            fclose (zip->fp);
            free (zip);
            return NULL;
      }

      if (fread (zip->cd, zip->size_of_cent_dir, 1, zip->fp) != 1)
      {
            free (zip->cd);
            free (zip->ecd);
            fclose (zip->fp);
            free (zip);
            return NULL;
      }

      /* reset ent */
      zip->ent.name = NULL;

      /* rewind */
      zip->cd_pos = 0;

      return zip;
}

/**
* jclass_jar_get_next_entry
* @jar: opened jar.
*
* Reads the current entry from a jar stream and
* advances the current entry "pointer".
*
* Returns: A pointer to the entry on success, or
* NULL if any errors occured while reading the entry.
*/
const JarEntry* jclass_jar_get_next_entry(JarFile *zip)
{

      /* end of directory */
      if (zip->cd_pos >= zip->size_of_cent_dir)
            return NULL;

      /* compile zipent info */
      zip->ent.version_needed_to_extract =
            *(zip->cd + zip->cd_pos + ZIPCVXT);
      zip->ent.os_needed_to_extract = *(zip->cd + zip->cd_pos + ZIPCEXOS);
      zip->ent.compression_method =
            read_word (zip->cd + zip->cd_pos + ZIPCMTHD);
      zip->ent.compressed_size =
            read_dword (zip->cd + zip->cd_pos + ZIPCSIZ);
      zip->ent.uncompressed_size =
            read_dword (zip->cd + zip->cd_pos + ZIPCUNC);
      zip->ent.filename_length =
            read_word (zip->cd + zip->cd_pos + ZIPCFNL);
      zip->ent.extra_field_length =
            read_word (zip->cd + zip->cd_pos + ZIPCXTL);
      zip->ent.file_comment_length =
            read_word (zip->cd + zip->cd_pos + ZIPCCML);
      zip->ent.disk_number_start =
            read_word (zip->cd + zip->cd_pos + ZIPDSK);
      zip->ent.offset_lcl_hdr_frm_frst_disk =
            read_dword (zip->cd + zip->cd_pos + ZIPOFST);

      /* check to see if filename length is illegally long (past the size of this directory
       * entry) */
      if (zip->cd_pos + ZIPCFN + zip->ent.filename_length >
          zip->size_of_cent_dir)
            return NULL;

      zip->ent.name =
            (char *) realloc (zip->ent.name,
                          zip->ent.filename_length + 1);
      memcpy (zip->ent.name, zip->cd + zip->cd_pos + ZIPCFN,
            zip->ent.filename_length);
      zip->ent.name[zip->ent.filename_length] = '\0';

      /* skip to next entry in central dir */
      zip->cd_pos +=
            ZIPCFN + zip->ent.filename_length +
            zip->ent.extra_field_length + zip->ent.file_comment_length;

      return &zip->ent;
}

/**
* jclass_jar_close
* @jar: The jar stream to close.
*
* Closes a jar stream and frees the memory allocated for it.
*/
void jclass_jar_close(JarFile* jar)
{
      /* release all */
      free(jar->ent.name);
      free(jar->cd);
      free(jar->ecd);
      fclose(jar->fp);
      free(jar);
}

/**
* jclass_jar_rewind
* @jar: The jar file to rewind.
*
* Rewinds a jar file (i.e. goes to the first file).
*/
void jclass_jar_rewind(JarFile* jar) {
      jar->cd_pos = 0;
}

/* Seek zip->fp to compressed data.
 * Returns 0 on success, < 0 on error.
*/
static int seekcompresszip (JarFile* zip, const JarEntry* ent)
{
      char buf[ZIPNAME];
      long offset;
      uint16_t filename_length;
      uint16_t extra_field_length;

      if (fseek (zip->fp, ent->offset_lcl_hdr_frm_frst_disk, SEEK_SET) != 0)
            return -1;

      if (fread (buf, ZIPNAME, 1, zip->fp) != 1)
            return -1;

      filename_length = read_word (buf + ZIPFNLN);
      extra_field_length = read_word (buf + ZIPXTRALN);

      /* calculate offset to data and fseek() there */
      offset = ent->offset_lcl_hdr_frm_frst_disk + ZIPNAME +
            filename_length + extra_field_length;

      if (fseek (zip->fp, offset, SEEK_SET) != 0)
            return -1;

      return 0;
}

/* Inflate a file
   in:
   in_file stream to inflate
   in_size size of the compressed data to read
   out_size size of decompressed data
   out:
   out_data buffer for decompressed data
   return:
   ==0 ok

   990525 rewritten for use with zlib MLR
*/
static int inflate_file (FILE * in_file, unsigned in_size, uint8_t * out_data,
                   unsigned out_size)
{
      int err;
      uint8_t *in_buffer;
      z_stream d_stream;      /* decompression stream */

      d_stream.zalloc = 0;
      d_stream.zfree = 0;
      d_stream.opaque = 0;

      d_stream.next_in = 0;
      d_stream.avail_in = 0;
      d_stream.next_out = out_data;
      d_stream.avail_out = out_size;

      err = inflateInit2 (&d_stream, -MAX_WBITS);
      /* windowBits is passed < 0 to tell that there is no zlib header.
       * Note that in this case inflate *requires* an extra "dummy" byte
       * after the compressed stream in order to complete decompression and
       * return Z_STREAM_END.
       */
      if (err != Z_OK)
            return -1;

      in_buffer = (uint8_t *) malloc (INFLATE_INPUT_BUFFER_MAX + 1);
      if (!in_buffer)
            return -1;

      for (;;)
      {
            if (in_size <= 0)
            {
                  free (in_buffer);
                  return -1;
            }
            d_stream.next_in = in_buffer;
            d_stream.avail_in =
                  fread (in_buffer, 1,
                         MIN (in_size, INFLATE_INPUT_BUFFER_MAX),
                         in_file);
            in_size -= d_stream.avail_in;
            if (in_size == 0)
                  d_stream.avail_in++;    /* add dummy byte at end of compressed data */

            err = inflate (&d_stream, Z_NO_FLUSH);
            
            if (err == Z_STREAM_END)
                  break;
            
            if (err != Z_OK)
            {
                  free (in_buffer);
                  return -1;
            }
            
      }

      err = inflateEnd (&d_stream);
      if (err != Z_OK)
      {
            free (in_buffer);
            return -1;
      }

      free (in_buffer);

      if ((d_stream.avail_out > 0) || (in_size > 0))
            return -1;

      return 0;
}

/* Read compressed data.
   out:
      data compressed data read
   @return
      1 success
      0 error
*/
static int readcompresszip (JarFile* zip, const JarEntry* ent, char* data)
{
      if (seekcompresszip(zip, ent) != 0)
            return 0;

      if (fread(data, ent->compressed_size, 1, zip->fp) != 1)
            return 0;

      return 1;
}

/*
* Reads a jar entry into the given buffer.
* It returns 1 if it succeeds
*/
static int _jar_entry_read(JarFile* jar, const JarEntry* entry, char *data) {
      switch (entry->compression_method)
      {
            /* file is not compressed, simply stored */
            case 0x0000:
                  /* check if size are equal */
                  if (entry->compressed_size == entry->uncompressed_size)
                        return !readcompresszip(jar, entry, data);
                  else
                        return 0;
            /* file is compressed using "Deflate" method */
            case 0x0008:
                  if ((entry->version_needed_to_extract > 0x14) ||
                      (entry->os_needed_to_extract != 0x00) ||
                      (entry->disk_number_start != jar->number_of_this_disk))
                  return 0;

                  /* read compressed data */
                  if (seekcompresszip (jar, entry) == 0)
                  {
                        /* configure inflate */
                        return !inflate_file(jar->fp, entry->compressed_size, (uint8_t*) data,
                             entry->uncompressed_size);
                  }
                  else
                        return 0;
            default:
                  return 0;
      }
}

/**
* jclass_jar_entry_read
* @jar: The jar file containing the entry.
* @entry: The entry to read data from.
*
* Loads the contents of a zip entry into a char buffer.
*
* Returns: A char buffer alloced with malloc on success, NULL otherwise.
*/
char* jclass_jar_entry_read(JarFile* jar, const JarEntry* entry)
{
      char *data;

      switch (entry->compression_method)
      {
            /* file is not compressed, simply stored */
      case 0x0000:
            /* check if size are equal */
            if (entry->compressed_size == entry->uncompressed_size)
            {
                  data = (char*) malloc (entry->uncompressed_size);
                  if (!readcompresszip(jar, entry, data))
                  {
                        free(data);
                        data = NULL;
                  }
            }
            else
                  data = NULL;

            break;

            /* file is compressed using "Deflate" method */
      case 0x0008:
            if ((entry->version_needed_to_extract > 0x14) ||
                (entry->os_needed_to_extract != 0x00) ||
                (entry->disk_number_start != jar->number_of_this_disk))
                  return NULL;

            /* read compressed data */
            if (seekcompresszip (jar, entry) == 0)
            {
                  /* configure inflate */
                  data = (char*) malloc (entry->uncompressed_size);
                  if (inflate_file
                      (jar->fp, entry->compressed_size, (uint8_t*) data,
                       entry->uncompressed_size) != 0)
                  {
                        free(data);
                        data = NULL;
                  }
            }
            else
                  data = NULL;

            break;
      default:
            data = NULL;
      }
      return data;
}

/**
* jclass_jar_get_entry
* @jar: The jar file to get the entry from.
* @name: The name of the entry. Path seperator is always '/'.
*
* Gives the JarEntry with the given name.
*
* Returns: A JarEntry you should not modify.
*/
const JarEntry* jclass_jar_get_entry(JarFile* jar, const char* name)
{
      const JarEntry* jarentry;

      jclass_jar_rewind(jar);

      while((jarentry = jclass_jar_get_next_entry(jar)) != NULL &&
            strcmp(jarentry->name, name));

      return jarentry;
}

/**
* jclass_jar_entry_get_name
* @entry: The entry to get its name.
*
* Gives the name of the given JarEntry.
*
* Returns: A string you should not modify.
*/
const char* jclass_jar_entry_get_name(const JarEntry* entry) {
      return entry->name;
}

/**
* jclass_jar_entry_get_size
* @entry: The entry to get its size.
*
* Gives the size of the given JarEntry.
*
* Returns: The size of the entry as an unsigned 32-bit integer.
*/
uint32_t jclass_jar_entry_get_size(const JarEntry *entry) {
      return entry->uncompressed_size;
}

/**
* jclass_jar_get_manifest
* @jar: The jar file to get its manifest.
*
* Gets the manifest for the given jar.
*
* Returns: A Manifest struct or NULL if something went wrong.
*/
Manifest *jclass_jar_get_manifest(JarFile *jar) {
      const JarEntry* jarentry;
      char *data;
      Manifest *manifest;
      jarentry = jclass_jar_get_entry(jar, "META-INF/MANIFEST.MF");
      
      if (!jarentry)
            return NULL;

      data = (char *)malloc(jarentry->uncompressed_size + 1);
      if (!data)
            return NULL;

      if (!_jar_entry_read(jar, jarentry, data)) {
            free(data);
            return NULL;
      }
      /* NULL terminate the file */
      data[jarentry->uncompressed_size] = '\0';

      manifest = jclass_manifest_new_from_buffer(data, 0);
      free(data);
      return manifest;
}

Generated by  Doxygen 1.6.0   Back to index