private CentralDirectoryVisitor centralDirectoryVisitor() { return new CentralDirectoryVisitor() { @Override public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { } @Override public void visitFileHeader(CentralDirectoryFileHeader fileHeader, int dataOffset) { AsciiBytes name = fileHeader.getName(); if (name.startsWith(META_INF) && name.endsWith(SIGNATURE_FILE_EXTENSION)) { JarFile.this.signed = true; } } @Override public void visitEnd() { } }; }
/** * Create a new {@link CentralDirectoryEndRecord} instance from the specified * {@link RandomAccessData}, searching backwards from the end until a valid block is * located. * @param data the source data * @throws IOException in case of I/O errors */ CentralDirectoryEndRecord(RandomAccessData data) throws IOException { this.block = createBlockFromEndOfData(data, READ_BLOCK_SIZE); this.size = MINIMUM_SIZE; this.offset = this.block.length - this.size; while (!isValid()) { this.size++; if (this.size > this.block.length) { if (this.size >= MAXIMUM_SIZE || this.size > data.getSize()) { throw new IOException("Unable to find ZIP central directory " + "records after reading " + this.size + " bytes"); } this.block = createBlockFromEndOfData(data, this.size + READ_BLOCK_SIZE); } this.offset = this.block.length - this.size; } }
/** * Return the underlying {@link RandomAccessData} for this entry. Generally this * method should not be called directly and instead data should be accessed via * {@link JarFile#getInputStream(ZipEntry)}. * @return the data * @throws IOException if the data cannot be read */ public RandomAccessData getData() throws IOException { if (this.data == null) { // aspectjrt-1.7.4.jar has a different ext bytes length in the // local directory to the central directory. We need to re-read // here to skip them byte[] localHeader = Bytes.get(this.source.getData() .getSubsection(this.localHeaderOffset, LOCAL_FILE_HEADER_SIZE)); long nameLength = Bytes.littleEndianValue(localHeader, 26, 2); long extraLength = Bytes.littleEndianValue(localHeader, 28, 2); this.data = this.source.getData().getSubsection(this.localHeaderOffset + LOCAL_FILE_HEADER_SIZE + nameLength + extraLength, getCompressedSize()); } return this.data; }
private List<JarEntryData> loadJarEntries(CentralDirectoryEndRecord endRecord) throws IOException { RandomAccessData centralDirectory = endRecord.getCentralDirectory(this.data); int numberOfRecords = endRecord.getNumberOfRecords(); List<JarEntryData> entries = new ArrayList<JarEntryData>(numberOfRecords); InputStream inputStream = centralDirectory.getInputStream(ResourceAccess.ONCE); try { JarEntryData entry = JarEntryData.fromInputStream(this, inputStream); while (entry != null) { entries.add(entry); processEntry(entry); entry = JarEntryData.fromInputStream(this, inputStream); } } finally { inputStream.close(); } return entries; }
public static byte[] get(RandomAccessData data) throws IOException { InputStream inputStream = data.getInputStream(ResourceAccess.ONCE); try { return get(inputStream, data.getSize()); } finally { inputStream.close(); } }
private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, JarEntryFilter filter, JarFileType type) throws IOException { super(rootFile.getFile()); this.rootFile = rootFile; this.pathFromRoot = pathFromRoot; CentralDirectoryParser parser = new CentralDirectoryParser(); this.entries = parser.addVisitor(new JarFileEntries(this, filter)); parser.addVisitor(centralDirectoryVisitor()); this.data = parser.parse(data, filter == null); this.type = type; }
private JarFile createJarFileFromFileEntry(JarEntry entry) throws IOException { if (entry.getMethod() != ZipEntry.STORED) { throw new IllegalStateException("Unable to open nested entry '" + entry.getName() + "'. It has been compressed and nested " + "jar files must be stored without compression. Please check the " + "mechanism used to create your executable jar file"); } RandomAccessData entryData = this.entries.getEntryData(entry.getName()); return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName(), entryData, JarFileType.NESTED_JAR); }
/** * Parse the source data, triggering {@link CentralDirectoryVisitor visitors}. * @param data the source data * @param skipPrefixBytes if prefix bytes should be skipped * @return The actual archive data without any prefix bytes * @throws IOException on error */ public RandomAccessData parse(RandomAccessData data, boolean skipPrefixBytes) throws IOException { CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data); if (skipPrefixBytes) { data = getArchiveData(endRecord, data); } RandomAccessData centralDirectoryData = endRecord.getCentralDirectory(data); visitStart(endRecord, centralDirectoryData); parseEntries(endRecord, centralDirectoryData); visitEnd(); return data; }
private void parseEntries(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) throws IOException { byte[] bytes = Bytes.get(centralDirectoryData); CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); int dataOffset = 0; for (int i = 0; i < endRecord.getNumberOfRecords(); i++) { fileHeader.load(bytes, dataOffset, null, 0, null); visitFileHeader(dataOffset, fileHeader); dataOffset += this.CENTRAL_DIRECTORY_HEADER_BASE_SIZE + fileHeader.getName().length() + fileHeader.getComment().length() + fileHeader.getExtra().length; } }
private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord, RandomAccessData data) { long offset = endRecord.getStartOfArchive(data); if (offset == 0) { return data; } return data.getSubsection(offset, data.getSize() - offset); }
@Override public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { int maxSize = endRecord.getNumberOfRecords(); this.centralDirectoryData = centralDirectoryData; this.hashCodes = new int[maxSize]; this.centralDirectoryOffsets = new int[maxSize]; this.positions = new int[maxSize]; }
public RandomAccessData getEntryData(String name) throws IOException { FileHeader entry = getEntry(name, FileHeader.class, false); if (entry == null) { return null; } return getEntryData(entry); }
private RandomAccessData getEntryData(FileHeader entry) throws IOException { // aspectjrt-1.7.4.jar has a different ext bytes length in the // local directory to the central directory. We need to re-read // here to skip them RandomAccessData data = this.jarFile.getData(); byte[] localHeader = Bytes.get( data.getSubsection(entry.getLocalHeaderOffset(), LOCAL_FILE_HEADER_SIZE)); long nameLength = Bytes.littleEndianValue(localHeader, 26, 2); long extraLength = Bytes.littleEndianValue(localHeader, 28, 2); return data.getSubsection(entry.getLocalHeaderOffset() + LOCAL_FILE_HEADER_SIZE + nameLength + extraLength, entry.getCompressedSize()); }
void load(byte[] data, int dataOffset, RandomAccessData variableData, int variableOffset, JarEntryFilter filter) throws IOException { // Load fixed part this.header = data; this.headerOffset = dataOffset; long nameLength = Bytes.littleEndianValue(data, dataOffset + 28, 2); long extraLength = Bytes.littleEndianValue(data, dataOffset + 30, 2); long commentLength = Bytes.littleEndianValue(data, dataOffset + 32, 2); this.localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4); // Load variable part dataOffset += 46; if (variableData != null) { data = Bytes.get(variableData.getSubsection(variableOffset + 46, nameLength + extraLength + commentLength)); dataOffset = 0; } this.name = new AsciiBytes(data, dataOffset, (int) nameLength); if (filter != null) { this.name = filter.apply(this.name); } this.extra = NO_EXTRA; this.comment = NO_COMMENT; if (extraLength > 0) { this.extra = new byte[(int) extraLength]; System.arraycopy(data, (int) (dataOffset + nameLength), this.extra, 0, this.extra.length); } if (commentLength > 0) { this.comment = new AsciiBytes(data, (int) (dataOffset + nameLength + extraLength), (int) commentLength); } }
public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data, int offset, JarEntryFilter filter) throws IOException { CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); byte[] bytes = Bytes.get(data.getSubsection(offset, 46)); fileHeader.load(bytes, 0, data, offset, filter); return fileHeader; }
@Test public void visitsInOrder() throws Exception { CentralDirectoryVisitor visitor = mock(TestCentralDirectoryVisitor.class); CentralDirectoryParser parser = new CentralDirectoryParser(); parser.addVisitor(visitor); parser.parse(this.jarData, false); InOrder ordered = inOrder(visitor); ordered.verify(visitor).visitStart(any(CentralDirectoryEndRecord.class), any(RandomAccessData.class)); ordered.verify(visitor, atLeastOnce()) .visitFileHeader(any(CentralDirectoryFileHeader.class), anyInt()); ordered.verify(visitor).visitEnd(); }
private void parseEntries(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) throws IOException { byte[] bytes = Bytes.get(centralDirectoryData); CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); int dataOffset = 0; for (int i = 0; i < endRecord.getNumberOfRecords(); i++) { fileHeader.load(bytes, dataOffset, null, 0); visitFileHeader(dataOffset, fileHeader); dataOffset += this.CENTRAL_DIRECTORY_HEADER_BASE_SIZE + fileHeader.getName().length() + fileHeader.getComment().length() + fileHeader.getExtra().length; } }
void load(byte[] data, int dataOffset, RandomAccessData variableData, int variableOffset) throws IOException { // Load fixed part this.header = data; this.headerOffset = dataOffset; long nameLength = Bytes.littleEndianValue(data, dataOffset + 28, 2); long extraLength = Bytes.littleEndianValue(data, dataOffset + 30, 2); long commentLength = Bytes.littleEndianValue(data, dataOffset + 32, 2); this.localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4); // Load variable part dataOffset += 46; if (variableData != null) { data = Bytes.get(variableData.getSubsection(variableOffset + 46, nameLength + extraLength + commentLength)); dataOffset = 0; } this.name = new AsciiBytes(data, dataOffset, (int) nameLength); this.extra = NO_EXTRA; this.comment = NO_COMMENT; if (extraLength > 0) { this.extra = new byte[(int) extraLength]; System.arraycopy(data, (int) (dataOffset + nameLength), this.extra, 0, this.extra.length); } if (commentLength > 0) { this.comment = new AsciiBytes(data, (int) (dataOffset + nameLength + extraLength), (int) commentLength); } }
public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data, int offset) throws IOException { CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); byte[] bytes = Bytes.get(data.getSubsection(offset, 46)); fileHeader.load(bytes, 0, data, offset); return fileHeader; }
/** * Private constructor used to create a new {@link JarFile} either directly or from a * nested entry. * @param rootFile the root jar file * @param pathFromRoot the name of this file * @param data the underlying data * @throws IOException if the file cannot be read */ private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data) throws IOException { super(rootFile.getFile()); CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data); this.rootFile = rootFile; this.pathFromRoot = pathFromRoot; this.data = getArchiveData(endRecord, data); this.entries = loadJarEntries(endRecord); }
private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, List<JarEntryData> entries, JarEntryFilter... filters) throws IOException { super(rootFile.getFile()); this.rootFile = rootFile; this.pathFromRoot = pathFromRoot; this.data = data; this.entries = filterEntries(entries, filters); }
RandomAccessData getData() { return this.data; }
private void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { for (CentralDirectoryVisitor visitor : this.visitors) { visitor.visitStart(endRecord, centralDirectoryData); } }
private byte[] createBlockFromEndOfData(RandomAccessData data, int size) throws IOException { int length = (int) Math.min(data.getSize(), size); return Bytes.get(data.getSubsection(data.getSize() - length, length)); }
void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData);
@Override public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { }