/* 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 #include #include #include 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; }