/*
* 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;
}
}