/* * 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 <cstring> #include <string> #include <cstdlib> #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