/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.util;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipException;
/**
* @author Ewout Prangsma (epr@users.sourceforge.net)
*/
public class JarBuffer implements JarConstants {
private final ByteBuffer buffer;
private final Map<String, ByteBuffer> entries;
private final Manifest manifest;
/**
* Initialize this instance.
*
* @param buffer
* @throws IOException
* @throws ZipException
*/
public JarBuffer(ByteBuffer buffer) throws ZipException, IOException {
this.buffer = buffer;
this.entries = readEntries();
this.manifest = readManifest();
}
/**
* Gets a map of jar entries.
*
* @return A map between the name and the data of each entry.
*/
public Map<String, ByteBuffer> entries() {
return Collections.unmodifiableMap(entries);
}
/**
* Returns the manifest for this JarFile or null when the JarFile does not
* contain a manifest file.
*/
public Manifest getManifest() throws IOException {
return manifest;
}
@SuppressWarnings("deprecation")
private Map<String, ByteBuffer> readEntries() throws ZipException,
IOException {
// Start at the beginning
buffer.rewind();
buffer.order(ByteOrder.LITTLE_ENDIAN);
/*
* Search for the End Of Central Directory. When a zip comment is
* present the directory may start earlier. FIXME: This searches the
* whole file in a very slow manner if the file isn't a zip file.
*/
int pos = buffer.limit() - ENDHDR;
for (; pos >= 0; pos--) {
if (buffer.getInt(pos) == ENDSIG) {
break;
}
}
if (pos < 0) {
throw new ZipException(
"central directory not found, probably not a zip file");
}
final int count = buffer.getShort(pos + ENDTOT);
// System.out.println("Count=" + count);
final int centralOffset = buffer.getInt(pos + ENDOFF);
// System.out.println("centralOffset=" + centralOffset);
HashMap<String, ByteBuffer> entries = new HashMap<String, ByteBuffer>(
count + count / 2);
buffer.position(centralOffset);
byte[] strBuf = new byte[16];
for (int i = 0; i < count; i++) {
pos = buffer.position();
buffer.position(buffer.position() + CENHDR);
if (buffer.getInt(pos + 0) != CENSIG) {
throw new ZipException("Wrong Central Directory signature "
+ NumberUtils.hex(buffer.getInt(pos + 0)));
}
int method = buffer.getShort(pos + CENHOW);
int dostime = buffer.getInt(pos + CENTIM);
int crc = buffer.getInt(pos + CENCRC);
int csize = buffer.getInt(pos + CENSIZ);
int size = buffer.getInt(pos + CENLEN);
int nameLen = buffer.getShort(pos + CENNAM);
int extraLen = buffer.getShort(pos + CENEXT);
int commentLen = buffer.getShort(pos + CENCOM);
int offset = buffer.getInt(pos + CENOFF);
int needBuffer = Math.max(nameLen, commentLen);
if (strBuf.length < needBuffer) {
strBuf = new byte[needBuffer];
}
buffer.get(strBuf, 0, nameLen);
String name = new String(strBuf, 0, 0, nameLen);
if (extraLen > 0) {
buffer.position(buffer.position() + extraLen);
}
if (commentLen > 0) {
buffer.get(strBuf, 0, commentLen);
}
// Slice of entry data
final int mark = buffer.position();
buffer.position(checkLocalHeader(buffer, offset, method));
final ByteBuffer entry = (ByteBuffer) buffer.slice().limit(size);
buffer.position(mark);
entries.put(name, entry);
}
return entries;
}
private int checkLocalHeader(ByteBuffer buffer, int offset, int method)
throws IOException {
if (buffer.getInt(offset + 0) != LOCSIG) {
throw new ZipException("Wrong Local header signature");
}
if (method != buffer.getShort(offset + LOCHOW)) {
throw new ZipException("Compression method mismatch");
}
final int nameLen = buffer.getShort(offset + LOCNAM);
final int extraLen = buffer.getShort(offset + LOCEXT);
return offset + LOCHDR + nameLen + extraLen;
}
private Manifest readManifest() throws IOException {
final ByteBuffer buf = entries.get(JarFile.MANIFEST_NAME);
if (buf == null) {
return null;
} else {
return new Manifest(new ByteBufferInputStream(buf));
}
}
public static void main(String[] args) throws SecurityException,
IOException {
FileChannel ch = new FileInputStream(args[0]).getChannel();
ByteBuffer buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size());
JarBuffer jb = new JarBuffer(buf);
for (Map.Entry<String, ByteBuffer> entry : jb.entries().entrySet()) {
final ByteBuffer ebuf = entry.getValue();
if (ebuf.limit() > 0) {
System.out.println(entry.getKey() + ' ' + ebuf.limit() + " 0x"
+ NumberUtils.hex(ebuf.getInt(0)));
}
}
}
}