jar.cpp revision 6b15695578f07a3f72c4c9475c1a261a3021472a
/*
* 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