diff options
Diffstat (limited to 'src/inkjar/jar.cpp')
| -rw-r--r-- | src/inkjar/jar.cpp | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/src/inkjar/jar.cpp b/src/inkjar/jar.cpp new file mode 100644 index 000000000..655f11cbb --- /dev/null +++ b/src/inkjar/jar.cpp @@ -0,0 +1,541 @@ +/* + * Copyright (C) 1999, 2000 Bryan Burns + * Copyright (C) 2004 Johan Ceuppens + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * TODO/FIXME: + * - configure #ifdefs should be enabled + * - move to cstdlib instead of stdlib.h etc. + * - remove exit functions + * - move to clean C++ code + * - windowsify + * - remove a few g_free/g_mallocs + * - unseekable files + * - move to LGPL by rewriting macros + * - crcs for compressed files + * - put in eof + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +//#ifdef STDC_HEADERS +//#endif + +//#ifdef HAVE_UNISTD_H +//#endif + +//#ifdef HAVE_SYS_PARAM_H +//#else +//#define MAXPATHLEN 1024 +//#endif + +//#ifdef HAVE_DIRENT_H +//#endif + +//#ifdef HAVE_FCNTL_H +#include <fcntl.h> +//#endif + + +#include <glib.h> + +#include "jar.h" + +#include <fstream> +#ifdef WORDS_BIGENDIAN + +#define L2BI(l) ((l & 0xff000000) >> 24) | \ +((l & 0x00ff0000) >> 8) | \ +((l & 0x0000ff00) << 8) | \ +((l & 0x000000ff) << 24); + +#define L2BS(l) ((l & 0xff00) >> 8) | ((l & 0x00ff) << 8); + +#endif + +namespace Inkjar { + +JarFile::JarFile(gchar const*new_filename) +{ + _filename = strdup(new_filename); + _last_filename = NULL; + fd = -1; +} + +//fixme: the following should probably just return a const gchar* and not +// use strdup +gchar *JarFile::get_last_filename() const +{ + return (_last_filename != NULL ? strdup(_last_filename) : NULL); +} + +JarFile::~JarFile() +{ + if (_filename != NULL) + g_free(_filename); + if (_last_filename != NULL) + g_free(_last_filename); +} + +bool JarFile::init_inflation() +{ + memset(&_zs, 0, sizeof(z_stream)); + + _zs.zalloc = Z_NULL; + _zs.zfree = Z_NULL; + _zs.opaque = Z_NULL; + + if(inflateInit2(&_zs, -15) != Z_OK) { + fprintf(stderr,"error initializing inflation!\n"); + return false; + } + + return true; +} + +bool JarFile::open() +{ + if ((fd = ::open(_filename, O_RDONLY)) < 0) { + fprintf(stderr, "open failed.\n"); + return false; + } + if (!init_inflation()) + return false; + + return true; +} + +bool JarFile::close() +{ + if (fd >= 0 && !::close(fd)) { + inflateEnd(&_zs); + return true; + } + return false; +} + +bool JarFile::read_signature() +{ + guint8 *bytes = (guint8 *)g_malloc(sizeof(guint8) * 4); + if (!read(bytes, 4)) { + g_free(bytes); + return false; + } + + guint32 signature = UNPACK_UB4(bytes, 0); + g_free(bytes); + +#ifdef DEBUG + std::printf("signature is %x\n", signature); +#endif + + if (signature == 0x08074b50) { + //skip data descriptor + bytes = (guint8 *)malloc(sizeof(guint8) * 12); + if (!read(bytes, 12)) { + g_free(bytes); + return false; + } + } else if (signature == 0x02014b50 || signature == 0x04034b50) { + return true; + } else { + return false; + } + return false; +} + +guint32 JarFile::get_crc(guint8 *bytes, guint16 flags) +{ + guint32 crc = 0; + //no data descriptor + if (!(flags & 0x0008)) { + crc = UNPACK_UB4(bytes, LOC_CRC); + +#ifdef DEBUG + std::printf("CRC from file is %x\n", crc); +#endif + } + + return crc; +} + +guint8 *JarFile::read_filename(guint16 filename_length) +{ + guint8 *filename = (guint8 *)g_malloc(sizeof(guint8) + * (filename_length+1)); + if (!read(filename, filename_length)) { + g_free(filename); + return NULL; + } + filename[filename_length] = '\0'; + +#ifdef DEBUG + std::printf("Filename is %s\n", filename); +#endif + + return filename; +} + +bool JarFile::check_compression_method(guint16 method, guint16 flags) +{ + return !(method != 8 && flags & 0x0008); +} + +GByteArray *JarFile::get_next_file_contents() +{ + guint8 *bytes; + GByteArray *gba = g_byte_array_new(); + + read_signature(); + + //get compressed size + bytes = (guint8 *)g_malloc(sizeof(guint8) * 30); + if (!read(bytes+4, 26)) { + g_free(bytes); + return NULL; + } + guint32 compressed_size = UNPACK_UB4(bytes, LOC_CSIZE); + guint16 filename_length = UNPACK_UB2(bytes, LOC_FNLEN); + guint16 eflen = UNPACK_UB2(bytes, LOC_EFLEN); + guint16 flags = UNPACK_UB2(bytes, LOC_EXTRA); + guint16 method = UNPACK_UB2(bytes, LOC_COMP); + + if (filename_length == 0) { + g_byte_array_free(gba, TRUE); + if (_last_filename != NULL) + g_free(_last_filename); + _last_filename = NULL; + return NULL; + } + + +#ifdef DEBUG + std::printf("Compressed size is %u\n", compressed_size); + std::printf("Filename length is %hu\n", filename_length); + std::printf("Extra field length is %hu\n", eflen); + std::printf("Flags are %#hx\n", flags); + std::printf("Compression method is %#hx\n", method); +#endif + + guint32 crc = get_crc(bytes, flags); + + gchar *filename = (gchar *)read_filename(filename_length); + g_free(bytes); + + if (filename == NULL) + return NULL; + + if (_last_filename != NULL) + g_free(_last_filename); + _last_filename = filename; + + //check if this is a directory and skip + + char *c_ptr; + if ((c_ptr = std::strrchr(filename, '/')) != NULL) { + if (*(++c_ptr) == '\0') { + return NULL; + } + } + + if (!check_compression_method(method, flags)) { + std::fprintf(stderr, "error in jar file\n"); + return NULL; + } + + if (method == 8 || flags & 0x0008) { + unsigned int file_length = 0;//uncompressed file length + lseek(fd, eflen, SEEK_CUR); + guint8 *file_data = get_compressed_file(compressed_size, file_length, + crc, flags); + if (file_data == NULL) { + g_byte_array_free(gba, FALSE); + return NULL; + } + g_byte_array_append(gba, file_data, file_length); + } else if (method == 0) { + guint8 *file_data = get_uncompressed_file(compressed_size, crc, + eflen, flags); + + if (file_data == NULL) { + g_byte_array_free(gba, TRUE); + return NULL; + } + g_byte_array_append(gba, file_data, compressed_size); + } else { + lseek(fd, compressed_size+eflen, SEEK_CUR); + g_byte_array_free(gba, FALSE); + return NULL; + } + + + return gba; +} + +guint8 *JarFile::get_uncompressed_file(guint32 compressed_size, guint32 crc, + guint16 eflen, guint16 flags) +{ + GByteArray *gba = g_byte_array_new(); + unsigned int out_a = 0; + unsigned int in_a = compressed_size; + guint8 *bytes; + guint32 crc2 = 0; + + crc2 = crc32(crc2, NULL, 0); + + bytes = (guint8 *)g_malloc(sizeof(guint8) * RDSZ); + while(out_a < compressed_size){ + unsigned int nbytes = (in_a > RDSZ ? RDSZ : in_a); + + if (!(nbytes = read(bytes, nbytes))) { + g_free(bytes); + return NULL; + } + + crc2 = crc32(crc2, (Bytef*)bytes, nbytes); + + g_byte_array_append (gba, bytes, nbytes); + out_a += nbytes; + in_a -= nbytes; + +#ifdef DEBUG + std::printf("%d bytes written\n", out_a); +#endif + } + lseek(fd, eflen, SEEK_CUR); + g_free(bytes); + + if (!check_crc(crc, crc2, flags)) { + bytes = gba->data; + g_byte_array_free(gba, FALSE);//FALSE argument does not free actual data + return NULL; + } + + return bytes; +} + +int JarFile::read(guint8 *buf, int count) +{ + int nbytes; + if ((nbytes = ::read(fd, buf, count)) != count) { + fprintf(stderr, "read error\n"); + exit(1); + return 0; + } + return nbytes; +} + +/* FIXME: this could probably use ZlibBuffer */ +guint8 *JarFile::get_compressed_file(guint32 compressed_size, + unsigned int& file_length, + guint32 oldcrc, guint16 flags) +{ + if (compressed_size == 0) + return NULL; + + guint8 in_buffer[RDSZ]; + guint8 out_buffer[RDSZ]; + int nbytes; + unsigned int leftover_in = compressed_size; + GByteArray *gba = g_byte_array_new(); + + _zs.avail_in = 0; + guint32 crc = crc32(0, Z_NULL, 0); + + do { + + if (!_zs.avail_in) { + + if ((nbytes = ::read(fd, in_buffer, + (leftover_in < RDSZ ? leftover_in : RDSZ))) + < 0) { + fprintf(stderr, "jarfile read error"); + } + _zs.avail_in = nbytes; + _zs.next_in = in_buffer; + crc = crc32(crc, in_buffer, _zs.avail_in); + leftover_in -= RDSZ; + } + _zs.next_out = out_buffer; + _zs.avail_out = RDSZ; + + int ret = inflate(&_zs, Z_NO_FLUSH); + if (RDSZ != _zs.avail_out) { + unsigned int tmp_len = RDSZ - _zs.avail_out; + guint8 *tmp_bytes = (guint8 *)g_malloc(sizeof(guint8) + * tmp_len); + memcpy(tmp_bytes, out_buffer, tmp_len); + g_byte_array_append(gba, tmp_bytes, tmp_len); + } + + if (ret == Z_STREAM_END) { + break; + } + if (ret != Z_OK) + std::printf("decompression error %d\n", ret); + } while (_zs.total_in < compressed_size); + + file_length = _zs.total_out; +#ifdef DEBUG + std::printf("done inflating\n"); + std::printf("%d bytes left over\n", _zs.avail_in); + std::printf("CRC is %x\n", crc); +#endif + + guint8 *ret_bytes; + if (check_crc(oldcrc, crc, flags) && gba->len > 0) + ret_bytes = gba->data; + else + ret_bytes = NULL; + g_byte_array_free(gba, FALSE); + + inflateReset(&_zs); + return ret_bytes; +} + +bool JarFile::check_crc(guint32 oldcrc, guint32 crc, guint16 flags) +{ + //fixme: does not work yet + + if(flags & 0x0008) { + guint8 *bytes = (guint8 *)g_malloc(sizeof(guint8) * 16); + if (!read(bytes, 16)) { + g_free(bytes); + return false; + } + + guint32 signature = UNPACK_UB4(bytes, 0); + g_free(bytes); + if(signature != 0x08074b50) { + fprintf(stderr, "missing data descriptor!\n"); + } + + crc = UNPACK_UB4(bytes, 4); + + } + if (oldcrc != crc) { +#ifdef DEBUG + std::fprintf(stderr, "Error! CRCs do not match! Got %x, expected %x\n", + oldcrc, crc); +#endif + } + return true; +} + +JarFile::JarFile(JarFile const& rhs) +{ + *this = rhs; +} + +JarFile& JarFile::operator=(JarFile const& rhs) +{ + if (this == &rhs) + return *this; + + _zs = rhs._zs;//fixme + if (_filename == NULL) + _filename = NULL; + else + _filename = strdup(rhs._filename); + if (_last_filename == NULL) + _last_filename = NULL; + else + _last_filename = strdup(rhs._last_filename); + fd = rhs.fd; + + return *this; +} + + +///////////////////////// +// JarFileReader // +///////////////////////// + +GByteArray *JarFileReader::get_next_file() +{ + if (_state == CLOSED) { + _jarfile.open(); + _state = OPEN; + } + + return _jarfile.get_next_file_contents(); +} + +JarFileReader& JarFileReader::operator=(JarFileReader const& rhs) +{ + if (&rhs == this) + return *this; + + _jarfile = rhs._jarfile; + _state = rhs._state; + + return *this; +} + +/* + * If the filename gets reset, a jarfile object gets generated again, + * ready to be opened for reading. + */ +void JarFileReader::set_filename(gchar const *new_filename) +{ + _jarfile.close(); + _jarfile = JarFile(new_filename); +} + +void JarFileReader::set_jarfile(JarFile const& new_jarfile) +{ + _jarfile = new_jarfile; +} + +JarFileReader::JarFileReader(JarFileReader const& rhs) +{ + *this = rhs; +} + +} // namespace Inkjar + + +#if 0 //testing code +#include "jar.h" +/* + * This program writes all the files from a jarfile to stdout and inflates + * where needed. + */ +int main(int argc, char *argv[]) +{ + gchar *filename; + if (argc < 2) { + filename = "./ide.jar\0"; + } else { + filename = argv[1]; + } + + Inkjar::JarFileReader jar_file_reader(filename); + + for (;;) { + GByteArray *gba = jar_file_reader.get_next_file(); + if (gba == NULL) { + char *c_ptr; + gchar *last_filename = jar_file_reader.get_last_filename(); + if (last_filename == NULL) + break; + if ((c_ptr = std::strrchr(last_filename, '/')) != NULL) { + if (*(++c_ptr) == '\0') { + g_free(last_filename); + continue; + } + } + } else if (gba->len > 0) + ::write(1, gba->data, gba->len); + else + break; + } + return 0; +} +#endif |
