/* * Copyright 2010 Srikanth Reddy Lingala * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.lingala.zip4j.core; import java.io.IOException; import java.io.RandomAccessFile; import java.util.ArrayList; import net.lingala.zip4j.exception.ZipException; import net.lingala.zip4j.exception.ZipExceptionConstants; import net.lingala.zip4j.model.AESExtraDataRecord; import net.lingala.zip4j.model.CentralDirectory; import net.lingala.zip4j.model.DigitalSignature; import net.lingala.zip4j.model.EndCentralDirRecord; import net.lingala.zip4j.model.ExtraDataRecord; import net.lingala.zip4j.model.FileHeader; import net.lingala.zip4j.model.LocalFileHeader; import net.lingala.zip4j.model.Zip64EndCentralDirLocator; import net.lingala.zip4j.model.Zip64EndCentralDirRecord; import net.lingala.zip4j.model.Zip64ExtendedInfo; import net.lingala.zip4j.model.ZipModel; import net.lingala.zip4j.util.InternalZipConstants; import net.lingala.zip4j.util.Raw; import net.lingala.zip4j.util.Zip4jConstants; import net.lingala.zip4j.util.Zip4jUtil; /** * Helper class to read header information for the zip file * */ public class HeaderReader { private RandomAccessFile zip4jRaf = null; private ZipModel zipModel; /** * Creates a new HeaderReader object with the given input stream * @param zip4jRaf */ public HeaderReader(RandomAccessFile zip4jRaf) { this.zip4jRaf = zip4jRaf; } /** * Reads all the header information for the zip file. * <br><br><b>Note:</b> This method does not read local file header information * @return {@link ZipModel} * @throws ZipException */ public ZipModel readAllHeaders() throws ZipException { return readAllHeaders(null); } /** * Reads all the header information for the zip file. File names are read with * input charset name. If this parameter is null, default system charset is used. * <br><br><b>Note:</b> This method does not read local file header information * @return {@link ZipModel} * @throws ZipException */ public ZipModel readAllHeaders(String fileNameCharset) throws ZipException { zipModel = new ZipModel(); zipModel.setFileNameCharset(fileNameCharset); zipModel.setEndCentralDirRecord(readEndOfCentralDirectoryRecord()); // If file is Zip64 format, then Zip64 headers have to be read before // reading central directory zipModel.setZip64EndCentralDirLocator(readZip64EndCentralDirLocator()); if (zipModel.isZip64Format()) { zipModel.setZip64EndCentralDirRecord(readZip64EndCentralDirRec()); if(zipModel.getZip64EndCentralDirRecord() != null && zipModel.getZip64EndCentralDirRecord().getNoOfThisDisk() > 0){ zipModel.setSplitArchive(true); } else { zipModel.setSplitArchive(false); } } zipModel.setCentralDirectory(readCentralDirectory()); //zipModel.setLocalFileHeaderList(readLocalFileHeaders()); //Donot read local headers now. return zipModel; } /** * Reads end of central directory record * @return {@link EndCentralDirRecord} * @throws ZipException */ private EndCentralDirRecord readEndOfCentralDirectoryRecord() throws ZipException { if (zip4jRaf == null) { throw new ZipException("random access file was null", ZipExceptionConstants.randomAccessFileNull); } try { byte[] ebs = new byte[4]; long pos = zip4jRaf.length() - InternalZipConstants.ENDHDR; EndCentralDirRecord endCentralDirRecord = new EndCentralDirRecord(); int counter = 0; do { zip4jRaf.seek(pos--); counter++; } while ((Raw.readLeInt(zip4jRaf, ebs) != InternalZipConstants.ENDSIG) && counter <= 3000); if ((Raw.readIntLittleEndian(ebs, 0) != InternalZipConstants.ENDSIG)) { throw new ZipException("zip headers not found. probably not a zip file"); } byte[] intBuff = new byte[4]; byte[] shortBuff = new byte[2]; //End of central record signature endCentralDirRecord.setSignature(InternalZipConstants.ENDSIG); //number of this disk readIntoBuff(zip4jRaf, shortBuff); endCentralDirRecord.setNoOfThisDisk(Raw.readShortLittleEndian(shortBuff, 0)); //number of the disk with the start of the central directory readIntoBuff(zip4jRaf, shortBuff); endCentralDirRecord.setNoOfThisDiskStartOfCentralDir(Raw.readShortLittleEndian(shortBuff, 0)); //total number of entries in the central directory on this disk readIntoBuff(zip4jRaf, shortBuff); endCentralDirRecord.setTotNoOfEntriesInCentralDirOnThisDisk(Raw.readShortLittleEndian(shortBuff, 0)); //total number of entries in the central directory readIntoBuff(zip4jRaf, shortBuff); endCentralDirRecord.setTotNoOfEntriesInCentralDir(Raw.readShortLittleEndian(shortBuff, 0)); //size of the central directory readIntoBuff(zip4jRaf, intBuff); endCentralDirRecord.setSizeOfCentralDir(Raw.readIntLittleEndian(intBuff, 0)); //offset of start of central directory with respect to the starting disk number readIntoBuff(zip4jRaf, intBuff); byte[] longBuff = getLongByteFromIntByte(intBuff); endCentralDirRecord.setOffsetOfStartOfCentralDir(Raw.readLongLittleEndian(longBuff, 0)); //.ZIP file comment length readIntoBuff(zip4jRaf, shortBuff); int commentLength = Raw.readShortLittleEndian(shortBuff, 0); endCentralDirRecord.setCommentLength(commentLength); //.ZIP file comment if (commentLength > 0) { byte[] commentBuf = new byte[commentLength]; readIntoBuff(zip4jRaf, commentBuf); endCentralDirRecord.setComment(new String(commentBuf)); endCentralDirRecord.setCommentBytes(commentBuf); } else { endCentralDirRecord.setComment(null); } int diskNumber = endCentralDirRecord.getNoOfThisDisk(); if (diskNumber > 0) { zipModel.setSplitArchive(true); } else { zipModel.setSplitArchive(false); } return endCentralDirRecord; } catch (IOException e) { throw new ZipException("Probably not a zip file or a corrupted zip file", e, ZipExceptionConstants.notZipFile); } } /** * Reads central directory information for the zip file * @return {@link CentralDirectory} * @throws ZipException */ private CentralDirectory readCentralDirectory() throws ZipException { if (zip4jRaf == null) { throw new ZipException("random access file was null", ZipExceptionConstants.randomAccessFileNull); } if (zipModel.getEndCentralDirRecord() == null) { throw new ZipException("EndCentralRecord was null, maybe a corrupt zip file"); } try { CentralDirectory centralDirectory = new CentralDirectory(); ArrayList fileHeaderList = new ArrayList(); EndCentralDirRecord endCentralDirRecord = zipModel.getEndCentralDirRecord(); long offSetStartCentralDir = endCentralDirRecord.getOffsetOfStartOfCentralDir(); int centralDirEntryCount = endCentralDirRecord.getTotNoOfEntriesInCentralDir(); if (zipModel.isZip64Format()) { offSetStartCentralDir = zipModel.getZip64EndCentralDirRecord().getOffsetStartCenDirWRTStartDiskNo(); centralDirEntryCount = (int)zipModel.getZip64EndCentralDirRecord().getTotNoOfEntriesInCentralDir(); } zip4jRaf.seek(offSetStartCentralDir); byte[] intBuff = new byte[4]; byte[] shortBuff = new byte[2]; byte[] longBuff = new byte[8]; for (int i = 0; i < centralDirEntryCount; i++) { FileHeader fileHeader = new FileHeader(); //FileHeader Signature readIntoBuff(zip4jRaf, intBuff); int signature = Raw.readIntLittleEndian(intBuff, 0); if (signature != InternalZipConstants.CENSIG) { throw new ZipException("Expected central directory entry not found (#" + (i + 1) + ")"); } fileHeader.setSignature(signature); //version made by readIntoBuff(zip4jRaf, shortBuff); fileHeader.setVersionMadeBy(Raw.readShortLittleEndian(shortBuff, 0)); //version needed to extract readIntoBuff(zip4jRaf, shortBuff); fileHeader.setVersionNeededToExtract(Raw.readShortLittleEndian(shortBuff, 0)); //general purpose bit flag readIntoBuff(zip4jRaf, shortBuff); fileHeader.setFileNameUTF8Encoded((Raw.readShortLittleEndian(shortBuff, 0) & InternalZipConstants.UFT8_NAMES_FLAG) != 0); int firstByte = shortBuff[0]; int result = firstByte & 1; if (result != 0) { fileHeader.setEncrypted(true); } fileHeader.setGeneralPurposeFlag((byte[])shortBuff.clone()); //Check if data descriptor exists for local file header fileHeader.setDataDescriptorExists(firstByte>>3 == 1); //compression method readIntoBuff(zip4jRaf, shortBuff); fileHeader.setCompressionMethod(Raw.readShortLittleEndian(shortBuff, 0)); //last mod file time readIntoBuff(zip4jRaf, intBuff); fileHeader.setLastModFileTime(Raw.readIntLittleEndian(intBuff, 0)); //crc-32 readIntoBuff(zip4jRaf, intBuff); fileHeader.setCrc32(Raw.readIntLittleEndian(intBuff, 0)); fileHeader.setCrcBuff((byte[])intBuff.clone()); //compressed size readIntoBuff(zip4jRaf, intBuff); longBuff = getLongByteFromIntByte(intBuff); fileHeader.setCompressedSize(Raw.readLongLittleEndian(longBuff, 0)); //uncompressed size readIntoBuff(zip4jRaf, intBuff); longBuff = getLongByteFromIntByte(intBuff); fileHeader.setUncompressedSize(Raw.readLongLittleEndian(longBuff, 0)); //file name length readIntoBuff(zip4jRaf, shortBuff); int fileNameLength = Raw.readShortLittleEndian(shortBuff, 0); fileHeader.setFileNameLength(fileNameLength); //extra field length readIntoBuff(zip4jRaf, shortBuff); int extraFieldLength = Raw.readShortLittleEndian(shortBuff, 0); fileHeader.setExtraFieldLength(extraFieldLength); //file comment length readIntoBuff(zip4jRaf, shortBuff); int fileCommentLength = Raw.readShortLittleEndian(shortBuff, 0); fileHeader.setFileComment(new String(shortBuff)); //disk number start readIntoBuff(zip4jRaf, shortBuff); fileHeader.setDiskNumberStart(Raw.readShortLittleEndian(shortBuff, 0)); //internal file attributes readIntoBuff(zip4jRaf, shortBuff); fileHeader.setInternalFileAttr((byte[])shortBuff.clone()); //external file attributes readIntoBuff(zip4jRaf, intBuff); fileHeader.setExternalFileAttr((byte[])intBuff.clone()); //relative offset of local header readIntoBuff(zip4jRaf, intBuff); //Commented on 26.08.2010. Revert back if any issues //fileHeader.setOffsetLocalHeader((Raw.readIntLittleEndian(intBuff, 0) & 0xFFFFFFFFL) + zip4jRaf.getStart()); longBuff = getLongByteFromIntByte(intBuff); fileHeader.setOffsetLocalHeader((Raw.readLongLittleEndian(longBuff, 0) & 0xFFFFFFFFL)); if (fileNameLength > 0) { byte[] fileNameBuf = new byte[fileNameLength]; readIntoBuff(zip4jRaf, fileNameBuf); // Modified after user reported an issue http://www.lingala.net/zip4j/forum/index.php?topic=2.0 // String fileName = new String(fileNameBuf, "Cp850"); // Modified as per http://www.lingala.net/zip4j/forum/index.php?topic=41.0 // String fileName = Zip4jUtil.getCp850EncodedString(fileNameBuf); String fileName = null; if (Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getFileNameCharset())) { fileName = new String(fileNameBuf, zipModel.getFileNameCharset()); } else { fileName = Zip4jUtil.decodeFileName(fileNameBuf, fileHeader.isFileNameUTF8Encoded()); } if (fileName == null) { throw new ZipException("fileName is null when reading central directory"); } if (fileName.indexOf(":" + System.getProperty("file.separator")) >= 0) { fileName = fileName.substring(fileName.indexOf(":" + System.getProperty("file.separator")) + 2); } fileHeader.setFileName(fileName); fileHeader.setDirectory(fileName.endsWith("/") || fileName.endsWith("\\")); } else { fileHeader.setFileName(null); } //Extra field readAndSaveExtraDataRecord(fileHeader); //Read Zip64 Extra data records if exists readAndSaveZip64ExtendedInfo(fileHeader); //Read AES Extra Data record if exists readAndSaveAESExtraDataRecord(fileHeader); // if (fileHeader.isEncrypted()) { // // if (fileHeader.getEncryptionMethod() == ZipConstants.ENC_METHOD_AES) { // //Do nothing // } else { // if ((firstByte & 64) == 64) { // //hardcoded for now // fileHeader.setEncryptionMethod(1); // } else { // fileHeader.setEncryptionMethod(ZipConstants.ENC_METHOD_STANDARD); // fileHeader.setCompressedSize(fileHeader.getCompressedSize() // - ZipConstants.STD_DEC_HDR_SIZE); // } // } // // } if (fileCommentLength > 0) { byte[] fileCommentBuf = new byte[fileCommentLength]; readIntoBuff(zip4jRaf, fileCommentBuf); fileHeader.setFileComment(new String(fileCommentBuf)); } fileHeaderList.add(fileHeader); } centralDirectory.setFileHeaders(fileHeaderList); //Digital Signature DigitalSignature digitalSignature = new DigitalSignature(); readIntoBuff(zip4jRaf, intBuff); int signature = Raw.readIntLittleEndian(intBuff, 0); if (signature != InternalZipConstants.DIGSIG) { return centralDirectory; } digitalSignature.setHeaderSignature(signature); //size of data readIntoBuff(zip4jRaf, shortBuff); int sizeOfData = Raw.readShortLittleEndian(shortBuff, 0); digitalSignature.setSizeOfData(sizeOfData); if (sizeOfData > 0) { byte[] sigDataBuf = new byte[sizeOfData]; readIntoBuff(zip4jRaf, sigDataBuf); digitalSignature.setSignatureData(new String(sigDataBuf)); } return centralDirectory; } catch (IOException e) { throw new ZipException(e); } } /** * Reads extra data record and saves it in the {@link FileHeader} * @param fileHeader * @throws ZipException */ private void readAndSaveExtraDataRecord(FileHeader fileHeader) throws ZipException { if (zip4jRaf == null) { throw new ZipException("invalid file handler when trying to read extra data record"); } if (fileHeader == null) { throw new ZipException("file header is null"); } int extraFieldLength = fileHeader.getExtraFieldLength(); if (extraFieldLength <= 0) { return; } fileHeader.setExtraDataRecords(readExtraDataRecords(extraFieldLength)); } /** * Reads extra data record and saves it in the {@link LocalFileHeader} * @param localFileHeader * @throws ZipException */ private void readAndSaveExtraDataRecord(LocalFileHeader localFileHeader) throws ZipException { if (zip4jRaf == null) { throw new ZipException("invalid file handler when trying to read extra data record"); } if (localFileHeader == null) { throw new ZipException("file header is null"); } int extraFieldLength = localFileHeader.getExtraFieldLength(); if (extraFieldLength <= 0) { return; } localFileHeader.setExtraDataRecords(readExtraDataRecords(extraFieldLength)); } /** * Reads extra data records * @param extraFieldLength * @return ArrayList of {@link ExtraDataRecord} * @throws ZipException */ private ArrayList readExtraDataRecords(int extraFieldLength) throws ZipException { if (extraFieldLength <= 0) { return null; } try { byte[] extraFieldBuf = new byte[extraFieldLength]; zip4jRaf.read(extraFieldBuf); int counter = 0; ArrayList extraDataList = new ArrayList(); while(counter < extraFieldLength) { ExtraDataRecord extraDataRecord = new ExtraDataRecord(); int header = Raw.readShortLittleEndian(extraFieldBuf, counter); extraDataRecord.setHeader(header); counter = counter + 2; int sizeOfRec = Raw.readShortLittleEndian(extraFieldBuf, counter); if ((2 + sizeOfRec) > extraFieldLength) { sizeOfRec = Raw.readShortBigEndian(extraFieldBuf, counter); if ((2 + sizeOfRec) > extraFieldLength) { //If this is the case, then extra data record is corrupt //skip reading any further extra data records break; } } extraDataRecord.setSizeOfData(sizeOfRec); counter = counter + 2; if (sizeOfRec > 0) { byte[] data = new byte[sizeOfRec]; System.arraycopy(extraFieldBuf, counter, data, 0, sizeOfRec); extraDataRecord.setData(data); } counter = counter + sizeOfRec; extraDataList.add(extraDataRecord); } if (extraDataList.size() > 0) { return extraDataList; } else { return null; } } catch (IOException e) { throw new ZipException(e); } } /** * Reads Zip64 End Of Central Directory Locator * @return {@link Zip64EndCentralDirLocator} * @throws ZipException */ private Zip64EndCentralDirLocator readZip64EndCentralDirLocator() throws ZipException { if (zip4jRaf == null) { throw new ZipException("invalid file handler when trying to read Zip64EndCentralDirLocator"); } try { Zip64EndCentralDirLocator zip64EndCentralDirLocator = new Zip64EndCentralDirLocator(); setFilePointerToReadZip64EndCentralDirLoc(); byte[] intBuff = new byte[4]; byte[] longBuff = new byte[8]; readIntoBuff(zip4jRaf, intBuff); int signature = Raw.readIntLittleEndian(intBuff, 0); if (signature == InternalZipConstants.ZIP64ENDCENDIRLOC) { zipModel.setZip64Format(true); zip64EndCentralDirLocator.setSignature(signature); } else { zipModel.setZip64Format(false); return null; } readIntoBuff(zip4jRaf, intBuff); zip64EndCentralDirLocator.setNoOfDiskStartOfZip64EndOfCentralDirRec( Raw.readIntLittleEndian(intBuff, 0)); readIntoBuff(zip4jRaf, longBuff); zip64EndCentralDirLocator.setOffsetZip64EndOfCentralDirRec( Raw.readLongLittleEndian(longBuff, 0)); readIntoBuff(zip4jRaf, intBuff); zip64EndCentralDirLocator.setTotNumberOfDiscs(Raw.readIntLittleEndian(intBuff, 0)); return zip64EndCentralDirLocator; } catch (Exception e) { throw new ZipException(e); } } /** * Reads Zip64 End of Central Directory Record * @return {@link Zip64EndCentralDirRecord} * @throws ZipException */ private Zip64EndCentralDirRecord readZip64EndCentralDirRec() throws ZipException { if (zipModel.getZip64EndCentralDirLocator() == null) { throw new ZipException("invalid zip64 end of central directory locator"); } long offSetStartOfZip64CentralDir = zipModel.getZip64EndCentralDirLocator().getOffsetZip64EndOfCentralDirRec(); if (offSetStartOfZip64CentralDir < 0) { throw new ZipException("invalid offset for start of end of central directory record"); } try { zip4jRaf.seek(offSetStartOfZip64CentralDir); Zip64EndCentralDirRecord zip64EndCentralDirRecord = new Zip64EndCentralDirRecord(); byte[] shortBuff = new byte[2]; byte[] intBuff = new byte[4]; byte[] longBuff = new byte[8]; //signature readIntoBuff(zip4jRaf, intBuff); int signature = Raw.readIntLittleEndian(intBuff, 0); if (signature != InternalZipConstants.ZIP64ENDCENDIRREC) { throw new ZipException("invalid signature for zip64 end of central directory record"); } zip64EndCentralDirRecord.setSignature(signature); //size of zip64 end of central directory record readIntoBuff(zip4jRaf, longBuff); zip64EndCentralDirRecord.setSizeOfZip64EndCentralDirRec( Raw.readLongLittleEndian(longBuff, 0)); //version made by readIntoBuff(zip4jRaf, shortBuff); zip64EndCentralDirRecord.setVersionMadeBy(Raw.readShortLittleEndian(shortBuff, 0)); //version needed to extract readIntoBuff(zip4jRaf, shortBuff); zip64EndCentralDirRecord.setVersionNeededToExtract(Raw.readShortLittleEndian(shortBuff, 0)); //number of this disk readIntoBuff(zip4jRaf, intBuff); zip64EndCentralDirRecord.setNoOfThisDisk(Raw.readIntLittleEndian(intBuff, 0)); //number of the disk with the start of the central directory readIntoBuff(zip4jRaf, intBuff); zip64EndCentralDirRecord.setNoOfThisDiskStartOfCentralDir( Raw.readIntLittleEndian(intBuff, 0)); //total number of entries in the central directory on this disk readIntoBuff(zip4jRaf, longBuff); zip64EndCentralDirRecord.setTotNoOfEntriesInCentralDirOnThisDisk( Raw.readLongLittleEndian(longBuff, 0)); //total number of entries in the central directory readIntoBuff(zip4jRaf, longBuff); zip64EndCentralDirRecord.setTotNoOfEntriesInCentralDir( Raw.readLongLittleEndian(longBuff, 0)); //size of the central directory readIntoBuff(zip4jRaf, longBuff); zip64EndCentralDirRecord.setSizeOfCentralDir(Raw.readLongLittleEndian(longBuff, 0)); //offset of start of central directory with respect to the starting disk number readIntoBuff(zip4jRaf, longBuff); zip64EndCentralDirRecord.setOffsetStartCenDirWRTStartDiskNo( Raw.readLongLittleEndian(longBuff, 0)); //zip64 extensible data sector //44 is the size of fixed variables in this record long extDataSecSize = zip64EndCentralDirRecord.getSizeOfZip64EndCentralDirRec() - 44; if (extDataSecSize > 0) { byte[] extDataSecRecBuf = new byte[(int)extDataSecSize]; readIntoBuff(zip4jRaf, extDataSecRecBuf); zip64EndCentralDirRecord.setExtensibleDataSector(extDataSecRecBuf); } return zip64EndCentralDirRecord; } catch (IOException e) { throw new ZipException(e); } } /** * Reads Zip64 Extended info and saves it in the {@link FileHeader} * @param fileHeader * @throws ZipException */ private void readAndSaveZip64ExtendedInfo(FileHeader fileHeader) throws ZipException { if (fileHeader == null) { throw new ZipException("file header is null in reading Zip64 Extended Info"); } if (fileHeader.getExtraDataRecords() == null || fileHeader.getExtraDataRecords().size() <= 0) { return; } Zip64ExtendedInfo zip64ExtendedInfo = readZip64ExtendedInfo( fileHeader.getExtraDataRecords(), fileHeader.getUncompressedSize(), fileHeader.getCompressedSize(), fileHeader.getOffsetLocalHeader(), fileHeader.getDiskNumberStart()); if (zip64ExtendedInfo != null) { fileHeader.setZip64ExtendedInfo(zip64ExtendedInfo); if (zip64ExtendedInfo.getUnCompressedSize() != -1) fileHeader.setUncompressedSize(zip64ExtendedInfo.getUnCompressedSize()); if (zip64ExtendedInfo.getCompressedSize() != -1) fileHeader.setCompressedSize(zip64ExtendedInfo.getCompressedSize()); if (zip64ExtendedInfo.getOffsetLocalHeader() != -1) fileHeader.setOffsetLocalHeader(zip64ExtendedInfo.getOffsetLocalHeader()); if (zip64ExtendedInfo.getDiskNumberStart() != -1) fileHeader.setDiskNumberStart(zip64ExtendedInfo.getDiskNumberStart()); } } /** * Reads Zip64 Extended Info and saves it in the {@link LocalFileHeader} * @param localFileHeader * @throws ZipException */ private void readAndSaveZip64ExtendedInfo(LocalFileHeader localFileHeader) throws ZipException { if (localFileHeader == null) { throw new ZipException("file header is null in reading Zip64 Extended Info"); } if (localFileHeader.getExtraDataRecords() == null || localFileHeader.getExtraDataRecords().size() <= 0) { return; } Zip64ExtendedInfo zip64ExtendedInfo = readZip64ExtendedInfo( localFileHeader.getExtraDataRecords(), localFileHeader.getUncompressedSize(), localFileHeader.getCompressedSize(), -1, -1); if (zip64ExtendedInfo != null) { localFileHeader.setZip64ExtendedInfo(zip64ExtendedInfo); if (zip64ExtendedInfo.getUnCompressedSize() != -1) localFileHeader.setUncompressedSize(zip64ExtendedInfo.getUnCompressedSize()); if (zip64ExtendedInfo.getCompressedSize() != -1) localFileHeader.setCompressedSize(zip64ExtendedInfo.getCompressedSize()); } } /** * Reads Zip64 Extended Info * @param extraDataRecords * @param unCompressedSize * @param compressedSize * @param offsetLocalHeader * @param diskNumberStart * @return {@link Zip64ExtendedInfo} * @throws ZipException */ private Zip64ExtendedInfo readZip64ExtendedInfo( ArrayList extraDataRecords, long unCompressedSize, long compressedSize, long offsetLocalHeader, int diskNumberStart) throws ZipException { for (int i = 0; i < extraDataRecords.size(); i++) { ExtraDataRecord extraDataRecord = (ExtraDataRecord)extraDataRecords.get(i); if (extraDataRecord == null) { continue; } if (extraDataRecord.getHeader() == 0x0001) { Zip64ExtendedInfo zip64ExtendedInfo = new Zip64ExtendedInfo(); byte[] byteBuff = extraDataRecord.getData(); if (extraDataRecord.getSizeOfData() <= 0) { break; } byte[] longByteBuff = new byte[8]; byte[] intByteBuff = new byte[4]; int counter = 0; boolean valueAdded = false; if (((unCompressedSize & 0xFFFF) == 0xFFFF) && counter < extraDataRecord.getSizeOfData()) { System.arraycopy(byteBuff, counter, longByteBuff, 0, 8); long val = Raw.readLongLittleEndian(longByteBuff, 0); zip64ExtendedInfo.setUnCompressedSize(val); counter += 8; valueAdded = true; } if (((compressedSize & 0xFFFF) == 0xFFFF) && counter < extraDataRecord.getSizeOfData()) { System.arraycopy(byteBuff, counter, longByteBuff, 0, 8); long val = Raw.readLongLittleEndian(longByteBuff, 0); zip64ExtendedInfo.setCompressedSize(val); counter += 8; valueAdded = true; } if (((offsetLocalHeader & 0xFFFF) == 0xFFFF) && counter < extraDataRecord.getSizeOfData()) { System.arraycopy(byteBuff, counter, longByteBuff, 0, 8); long val = Raw.readLongLittleEndian(longByteBuff, 0); zip64ExtendedInfo.setOffsetLocalHeader(val); counter += 8; valueAdded = true; } if (((diskNumberStart & 0xFFFF) == 0xFFFF) && counter < extraDataRecord.getSizeOfData()) { System.arraycopy(byteBuff, counter, intByteBuff, 0, 4); int val = Raw.readIntLittleEndian(intByteBuff, 0); zip64ExtendedInfo.setDiskNumberStart(val); counter += 8; valueAdded = true; } if (valueAdded) { return zip64ExtendedInfo; } break; } } return null; } /** * Sets the current random access file pointer at the start of signature * of the zip64 end of central directory record * @throws ZipException */ private void setFilePointerToReadZip64EndCentralDirLoc() throws ZipException { try { byte[] ebs = new byte[4]; long pos = zip4jRaf.length() - InternalZipConstants.ENDHDR; do { zip4jRaf.seek(pos--); } while (Raw.readLeInt(zip4jRaf, ebs) != InternalZipConstants.ENDSIG); // Now the file pointer is at the end of signature of Central Dir Rec // Seek back with the following values // 4 -> end of central dir signature // 4 -> total number of disks // 8 -> relative offset of the zip64 end of central directory record // 4 -> number of the disk with the start of the zip64 end of central directory // 4 -> zip64 end of central dir locator signature // Refer to Appnote for more information //TODO: Donot harcorde these values. Make use of ZipConstants zip4jRaf.seek(zip4jRaf.getFilePointer() - 4 - 4 - 8 - 4 - 4); } catch (IOException e) { throw new ZipException(e); } } /** * Reads local file header for the given file header * @param fileHeader * @return {@link LocalFileHeader} * @throws ZipException */ public LocalFileHeader readLocalFileHeader(FileHeader fileHeader) throws ZipException { if (fileHeader == null || zip4jRaf == null) { throw new ZipException("invalid read parameters for local header"); } long locHdrOffset = fileHeader.getOffsetLocalHeader(); if (fileHeader.getZip64ExtendedInfo() != null) { Zip64ExtendedInfo zip64ExtendedInfo = fileHeader.getZip64ExtendedInfo(); if (zip64ExtendedInfo.getOffsetLocalHeader() > 0) { locHdrOffset = fileHeader.getOffsetLocalHeader(); } } if (locHdrOffset < 0) { throw new ZipException("invalid local header offset"); } try { zip4jRaf.seek(locHdrOffset); int length = 0; LocalFileHeader localFileHeader = new LocalFileHeader(); byte[] shortBuff = new byte[2]; byte[] intBuff = new byte[4]; byte[] longBuff = new byte[8]; //signature readIntoBuff(zip4jRaf, intBuff); int sig = Raw.readIntLittleEndian(intBuff, 0); if (sig != InternalZipConstants.LOCSIG) { throw new ZipException("invalid local header signature for file: " + fileHeader.getFileName()); } localFileHeader.setSignature(sig); length += 4; //version needed to extract readIntoBuff(zip4jRaf, shortBuff); localFileHeader.setVersionNeededToExtract(Raw.readShortLittleEndian(shortBuff, 0)); length += 2; //general purpose bit flag readIntoBuff(zip4jRaf, shortBuff); localFileHeader.setFileNameUTF8Encoded((Raw.readShortLittleEndian(shortBuff, 0) & InternalZipConstants.UFT8_NAMES_FLAG) != 0); int firstByte = shortBuff[0]; int result = firstByte & 1; if (result != 0) { localFileHeader.setEncrypted(true); } localFileHeader.setGeneralPurposeFlag(shortBuff); length += 2; //Check if data descriptor exists for local file header String binary = Integer.toBinaryString(firstByte); if (binary.length() >= 4) localFileHeader.setDataDescriptorExists(binary.charAt(3) == '1'); //compression method readIntoBuff(zip4jRaf, shortBuff); localFileHeader.setCompressionMethod(Raw.readShortLittleEndian(shortBuff, 0)); length += 2; //last mod file time readIntoBuff(zip4jRaf, intBuff); localFileHeader.setLastModFileTime(Raw.readIntLittleEndian(intBuff, 0)); length += 4; //crc-32 readIntoBuff(zip4jRaf, intBuff); localFileHeader.setCrc32(Raw.readIntLittleEndian(intBuff, 0)); localFileHeader.setCrcBuff((byte[])intBuff.clone()); length += 4; //compressed size readIntoBuff(zip4jRaf, intBuff); longBuff = getLongByteFromIntByte(intBuff); localFileHeader.setCompressedSize(Raw.readLongLittleEndian(longBuff, 0)); length += 4; //uncompressed size readIntoBuff(zip4jRaf, intBuff); longBuff = getLongByteFromIntByte(intBuff); localFileHeader.setUncompressedSize(Raw.readLongLittleEndian(longBuff, 0)); length += 4; //file name length readIntoBuff(zip4jRaf, shortBuff); int fileNameLength = Raw.readShortLittleEndian(shortBuff, 0); localFileHeader.setFileNameLength(fileNameLength); length += 2; //extra field length readIntoBuff(zip4jRaf, shortBuff); int extraFieldLength = Raw.readShortLittleEndian(shortBuff, 0); localFileHeader.setExtraFieldLength(extraFieldLength); length += 2; //file name if (fileNameLength > 0) { byte[] fileNameBuf = new byte[fileNameLength]; readIntoBuff(zip4jRaf, fileNameBuf); // Modified after user reported an issue http://www.lingala.net/zip4j/forum/index.php?topic=2.0 // String fileName = new String(fileNameBuf, "Cp850"); // String fileName = Zip4jUtil.getCp850EncodedString(fileNameBuf); String fileName = Zip4jUtil.decodeFileName(fileNameBuf, localFileHeader.isFileNameUTF8Encoded()); if (fileName == null) { throw new ZipException("file name is null, cannot assign file name to local file header"); } if (fileName.indexOf(":" + System.getProperty("file.separator")) >= 0) { fileName = fileName.substring(fileName.indexOf(":" + System.getProperty("file.separator")) + 2); } localFileHeader.setFileName(fileName); length += fileNameLength; } else { localFileHeader.setFileName(null); } //extra field readAndSaveExtraDataRecord(localFileHeader); length += extraFieldLength; localFileHeader.setOffsetStartOfData(locHdrOffset + length); //Copy password from fileHeader to localFileHeader localFileHeader.setPassword(fileHeader.getPassword()); readAndSaveZip64ExtendedInfo(localFileHeader); readAndSaveAESExtraDataRecord(localFileHeader); if (localFileHeader.isEncrypted()) { if (localFileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) { //Do nothing } else { if ((firstByte & 64) == 64) { //hardcoded for now localFileHeader.setEncryptionMethod(1); } else { localFileHeader.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD); // localFileHeader.setCompressedSize(localFileHeader.getCompressedSize() // - ZipConstants.STD_DEC_HDR_SIZE); } } } if (localFileHeader.getCrc32() <= 0) { localFileHeader.setCrc32(fileHeader.getCrc32()); localFileHeader.setCrcBuff(fileHeader.getCrcBuff()); } if (localFileHeader.getCompressedSize() <= 0) { localFileHeader.setCompressedSize(fileHeader.getCompressedSize()); } if (localFileHeader.getUncompressedSize() <= 0) { localFileHeader.setUncompressedSize(fileHeader.getUncompressedSize()); } return localFileHeader; } catch (IOException e) { throw new ZipException(e); } } /** * Reads AES Extra Data Record and saves it in the {@link FileHeader} * @param fileHeader * @throws ZipException */ private void readAndSaveAESExtraDataRecord(FileHeader fileHeader) throws ZipException { if (fileHeader == null) { throw new ZipException("file header is null in reading Zip64 Extended Info"); } if (fileHeader.getExtraDataRecords() == null || fileHeader.getExtraDataRecords().size() <= 0) { return; } AESExtraDataRecord aesExtraDataRecord = readAESExtraDataRecord(fileHeader.getExtraDataRecords()); if (aesExtraDataRecord != null) { fileHeader.setAesExtraDataRecord(aesExtraDataRecord); fileHeader.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES); } } /** * Reads AES Extra Data Record and saves it in the {@link LocalFileHeader} * @param localFileHeader * @throws ZipException */ private void readAndSaveAESExtraDataRecord(LocalFileHeader localFileHeader) throws ZipException { if (localFileHeader == null) { throw new ZipException("file header is null in reading Zip64 Extended Info"); } if (localFileHeader.getExtraDataRecords() == null || localFileHeader.getExtraDataRecords().size() <= 0) { return; } AESExtraDataRecord aesExtraDataRecord = readAESExtraDataRecord(localFileHeader.getExtraDataRecords()); if (aesExtraDataRecord != null) { localFileHeader.setAesExtraDataRecord(aesExtraDataRecord); localFileHeader.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES); } } /** * Reads AES Extra Data Record * @param extraDataRecords * @return {@link AESExtraDataRecord} * @throws ZipException */ private AESExtraDataRecord readAESExtraDataRecord(ArrayList extraDataRecords) throws ZipException { if (extraDataRecords == null) { return null; } for (int i = 0; i < extraDataRecords.size(); i++) { ExtraDataRecord extraDataRecord = (ExtraDataRecord)extraDataRecords.get(i); if (extraDataRecord == null) { continue; } if (extraDataRecord.getHeader() == InternalZipConstants.AESSIG) { if (extraDataRecord.getData() == null) { throw new ZipException("corrput AES extra data records"); } AESExtraDataRecord aesExtraDataRecord = new AESExtraDataRecord(); aesExtraDataRecord.setSignature(InternalZipConstants.AESSIG); aesExtraDataRecord.setDataSize(extraDataRecord.getSizeOfData()); byte[] aesData = extraDataRecord.getData(); aesExtraDataRecord.setVersionNumber(Raw.readShortLittleEndian(aesData, 0)); byte[] vendorIDBytes = new byte[2]; System.arraycopy(aesData, 2, vendorIDBytes, 0, 2); aesExtraDataRecord.setVendorID(new String(vendorIDBytes)); aesExtraDataRecord.setAesStrength((int)(aesData[4] & 0xFF)); aesExtraDataRecord.setCompressionMethod(Raw.readShortLittleEndian(aesData, 5)); return aesExtraDataRecord; } } return null; } /** * Reads buf length of bytes from the input stream to buf * @param zip4jRaf * @param buf * @return byte array * @throws ZipException */ private byte[] readIntoBuff(RandomAccessFile zip4jRaf, byte[] buf) throws ZipException { try { if (zip4jRaf.read(buf, 0, buf.length) != -1) { return buf; } else { throw new ZipException("unexpected end of file when reading short buff"); } } catch (IOException e) { throw new ZipException("IOException when reading short buff", e); } } /** * Returns a long byte from an int byte by appending last 4 bytes as 0's * @param intByte * @return byte array * @throws ZipException */ private byte[] getLongByteFromIntByte(byte[] intByte) throws ZipException { if (intByte == null) { throw new ZipException("input parameter is null, cannot expand to 8 bytes"); } if (intByte.length != 4) { throw new ZipException("invalid byte length, cannot expand to 8 bytes"); } byte[] longBuff = {intByte[0], intByte[1], intByte[2], intByte[3], 0, 0, 0, 0}; return longBuff; } }