/*
* 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.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.io.SplitOutputStream;
import net.lingala.zip4j.model.AESExtraDataRecord;
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.ZipModel;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.Raw;
import net.lingala.zip4j.util.Zip4jUtil;
public class HeaderWriter {
private final int ZIP64_EXTRA_BUF = 50;
public int writeLocalFileHeader(ZipModel zipModel, LocalFileHeader localFileHeader,
OutputStream outputStream) throws ZipException {
if (localFileHeader == null) {
throw new ZipException("input parameters are null, cannot write local file header");
}
try {
ArrayList byteArrayList = new ArrayList();
byte[] shortByte = new byte[2];
byte[] intByte = new byte[4];
byte[] longByte = new byte[8];
byte[] emptyLongByte = {0,0,0,0,0,0,0,0};
Raw.writeIntLittleEndian(intByte, 0, localFileHeader.getSignature());
copyByteArrayToArrayList(intByte, byteArrayList);
Raw.writeShortLittleEndian(shortByte, 0, (short)localFileHeader.getVersionNeededToExtract());
copyByteArrayToArrayList(shortByte, byteArrayList);
//General Purpose bit flags
copyByteArrayToArrayList(localFileHeader.getGeneralPurposeFlag(), byteArrayList);
//Compression Method
Raw.writeShortLittleEndian(shortByte, 0, (short)localFileHeader.getCompressionMethod());
copyByteArrayToArrayList(shortByte, byteArrayList);
//File modified time
int dateTime = localFileHeader.getLastModFileTime();
Raw.writeIntLittleEndian(intByte, 0, (int)dateTime);
copyByteArrayToArrayList(intByte, byteArrayList);
//Skip crc for now - this field will be updated after data is compressed
Raw.writeIntLittleEndian(intByte, 0, (int)localFileHeader.getCrc32());
copyByteArrayToArrayList(intByte, byteArrayList);
boolean writingZip64Rec = false;
//compressed & uncompressed size
long uncompressedSize = localFileHeader.getUncompressedSize();
if (uncompressedSize + ZIP64_EXTRA_BUF >= InternalZipConstants.ZIP_64_LIMIT) {
Raw.writeLongLittleEndian(longByte, 0, InternalZipConstants.ZIP_64_LIMIT);
System.arraycopy(longByte, 0, intByte, 0, 4);
//Set the uncompressed size to ZipConstants.ZIP_64_LIMIT as
//these values will be stored in Zip64 extra record
copyByteArrayToArrayList(intByte, byteArrayList);
copyByteArrayToArrayList(intByte, byteArrayList);
zipModel.setZip64Format(true);
writingZip64Rec = true;
localFileHeader.setWriteComprSizeInZip64ExtraRecord(true);
} else {
Raw.writeLongLittleEndian(longByte, 0, localFileHeader.getCompressedSize());
System.arraycopy(longByte, 0, intByte, 0, 4);
copyByteArrayToArrayList(intByte, byteArrayList);
Raw.writeLongLittleEndian(longByte, 0, localFileHeader.getUncompressedSize());
System.arraycopy(longByte, 0, intByte, 0, 4);
//Raw.writeIntLittleEndian(intByte, 0, (int)localFileHeader.getUncompressedSize());
copyByteArrayToArrayList(intByte, byteArrayList);
localFileHeader.setWriteComprSizeInZip64ExtraRecord(false);
}
Raw.writeShortLittleEndian(shortByte, 0, (short)localFileHeader.getFileNameLength());
copyByteArrayToArrayList(shortByte, byteArrayList);
// extra field length
int extraFieldLength = 0;
if (writingZip64Rec) {
extraFieldLength += 20;
}
if (localFileHeader.getAesExtraDataRecord() != null) {
extraFieldLength += 11;
}
Raw.writeShortLittleEndian(shortByte, 0, (short)(extraFieldLength));
copyByteArrayToArrayList(shortByte, byteArrayList);
if (Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getFileNameCharset())) {
byte[] fileNameBytes = localFileHeader.getFileName().getBytes(zipModel.getFileNameCharset());
copyByteArrayToArrayList(fileNameBytes, byteArrayList);
} else {
copyByteArrayToArrayList(Zip4jUtil.convertCharset(localFileHeader.getFileName()), byteArrayList);
}
//Zip64 should be the first extra data record that should be written
//This is NOT according to any specification but if this is changed
//then take care of updateLocalFileHeader for compressed size
if (writingZip64Rec) {
//Zip64 header
Raw.writeShortLittleEndian(shortByte, 0, (short)InternalZipConstants.EXTRAFIELDZIP64LENGTH);
copyByteArrayToArrayList(shortByte, byteArrayList);
//Zip64 extra data record size
//hardcoded it to 16 for local file header as we will just write
//compressed and uncompressed file sizes
Raw.writeShortLittleEndian(shortByte, 0, (short)16);
copyByteArrayToArrayList(shortByte, byteArrayList);
//uncompressed size
Raw.writeLongLittleEndian(longByte, 0, localFileHeader.getUncompressedSize());
copyByteArrayToArrayList(longByte, byteArrayList);
//set compressed size to 0 for now
copyByteArrayToArrayList(emptyLongByte, byteArrayList);
}
if (localFileHeader.getAesExtraDataRecord() != null) {
AESExtraDataRecord aesExtraDataRecord = localFileHeader.getAesExtraDataRecord();
Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getSignature());
copyByteArrayToArrayList(shortByte, byteArrayList);
Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getDataSize());
copyByteArrayToArrayList(shortByte, byteArrayList);
Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getVersionNumber());
copyByteArrayToArrayList(shortByte, byteArrayList);
copyByteArrayToArrayList(aesExtraDataRecord.getVendorID().getBytes(), byteArrayList);
byte[] aesStrengthBytes = new byte[1];
aesStrengthBytes[0] = (byte)aesExtraDataRecord.getAesStrength();
copyByteArrayToArrayList(aesStrengthBytes, byteArrayList);
Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getCompressionMethod());
copyByteArrayToArrayList(shortByte, byteArrayList);
}
byte[] lhBytes = byteArrayListToByteArray(byteArrayList);
outputStream.write(lhBytes);
return lhBytes.length;
} catch (ZipException e) {
throw e;
} catch (Exception e) {
throw new ZipException(e);
}
}
public int writeExtendedLocalHeader(LocalFileHeader localFileHeader,
OutputStream outputStream) throws ZipException, IOException {
if (localFileHeader == null || outputStream == null) {
throw new ZipException("input parameters is null, cannot write extended local header");
}
ArrayList byteArrayList = new ArrayList();
byte[] intByte = new byte[4];
//Extended local file header signature
Raw.writeIntLittleEndian(intByte, 0, (int)InternalZipConstants.EXTSIG);
copyByteArrayToArrayList(intByte, byteArrayList);
//CRC
Raw.writeIntLittleEndian(intByte, 0, (int)localFileHeader.getCrc32());
copyByteArrayToArrayList(intByte, byteArrayList);
//compressed size
long compressedSize = localFileHeader.getCompressedSize();
if (compressedSize >= Integer.MAX_VALUE) {
compressedSize = Integer.MAX_VALUE;
}
Raw.writeIntLittleEndian(intByte, 0, (int)compressedSize);
copyByteArrayToArrayList(intByte, byteArrayList);
//uncompressed size
long uncompressedSize = localFileHeader.getUncompressedSize();
if (uncompressedSize >= Integer.MAX_VALUE) {
uncompressedSize = Integer.MAX_VALUE;
}
Raw.writeIntLittleEndian(intByte, 0, (int)uncompressedSize);
copyByteArrayToArrayList(intByte, byteArrayList);
byte[] extLocHdrBytes = byteArrayListToByteArray(byteArrayList);
outputStream.write(extLocHdrBytes);
return extLocHdrBytes.length;
}
/**
* Processes zip header data and writes this data to the zip file
* @param zipModel
* @param outputStream
* @throws ZipException
*/
public void finalizeZipFile(ZipModel zipModel,
OutputStream outputStream) throws ZipException {
if (zipModel == null || outputStream == null) {
throw new ZipException("input parameters is null, cannot finalize zip file");
}
try {
processHeaderData(zipModel, outputStream);
long offsetCentralDir = zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir();
List headerBytesList = new ArrayList();
int sizeOfCentralDir = writeCentralDirectory(zipModel, outputStream, headerBytesList);
if (zipModel.isZip64Format()) {
if (zipModel.getZip64EndCentralDirRecord() == null) {
zipModel.setZip64EndCentralDirRecord(new Zip64EndCentralDirRecord());
}
if (zipModel.getZip64EndCentralDirLocator() == null) {
zipModel.setZip64EndCentralDirLocator(new Zip64EndCentralDirLocator());
}
zipModel.getZip64EndCentralDirLocator().setOffsetZip64EndOfCentralDirRec(offsetCentralDir + sizeOfCentralDir);
if (outputStream instanceof SplitOutputStream) {
zipModel.getZip64EndCentralDirLocator().setNoOfDiskStartOfZip64EndOfCentralDirRec(((SplitOutputStream)outputStream).getCurrSplitFileCounter());
zipModel.getZip64EndCentralDirLocator().setTotNumberOfDiscs(((SplitOutputStream)outputStream).getCurrSplitFileCounter() + 1);
} else {
zipModel.getZip64EndCentralDirLocator().setNoOfDiskStartOfZip64EndOfCentralDirRec(0);
zipModel.getZip64EndCentralDirLocator().setTotNumberOfDiscs(1);
}
writeZip64EndOfCentralDirectoryRecord(zipModel, outputStream, sizeOfCentralDir, offsetCentralDir, headerBytesList);
writeZip64EndOfCentralDirectoryLocator(zipModel, outputStream, headerBytesList);
}
writeEndOfCentralDirectoryRecord(zipModel, outputStream, sizeOfCentralDir, offsetCentralDir, headerBytesList);
writeZipHeaderBytes(zipModel, outputStream, byteArrayListToByteArray(headerBytesList));
} catch (ZipException e) {
throw e;
} catch (Exception e) {
throw new ZipException(e);
}
}
/**
* Processes zip header data and writes this data to the zip file without any validations.
* This process is not intended to use for normal operations (adding, deleting, etc) of a zip file.
* This method is used when certain validations need to be skipped (ex: Merging split zip files,
* adding comment to a zip file, etc)
* @param zipModel
* @param outputStream
* @throws ZipException
*/
public void finalizeZipFileWithoutValidations(ZipModel zipModel, OutputStream outputStream) throws ZipException {
if (zipModel == null || outputStream == null) {
throw new ZipException("input parameters is null, cannot finalize zip file without validations");
}
try {
List headerBytesList = new ArrayList();
long offsetCentralDir = zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir();
int sizeOfCentralDir = writeCentralDirectory(zipModel, outputStream, headerBytesList);
if (zipModel.isZip64Format()) {
if (zipModel.getZip64EndCentralDirRecord() == null) {
zipModel.setZip64EndCentralDirRecord(new Zip64EndCentralDirRecord());
}
if (zipModel.getZip64EndCentralDirLocator() == null) {
zipModel.setZip64EndCentralDirLocator(new Zip64EndCentralDirLocator());
}
zipModel.getZip64EndCentralDirLocator().setOffsetZip64EndOfCentralDirRec(offsetCentralDir + sizeOfCentralDir);
writeZip64EndOfCentralDirectoryRecord(zipModel, outputStream, sizeOfCentralDir, offsetCentralDir, headerBytesList);
writeZip64EndOfCentralDirectoryLocator(zipModel, outputStream, headerBytesList);
}
writeEndOfCentralDirectoryRecord(zipModel, outputStream, sizeOfCentralDir, offsetCentralDir, headerBytesList);
writeZipHeaderBytes(zipModel, outputStream, byteArrayListToByteArray(headerBytesList));
} catch(ZipException e) {
throw e;
} catch (Exception e) {
throw new ZipException(e);
}
}
/**
* Writes the zip header data to the zip file
* @param outputStream
* @param buff
* @throws ZipException
*/
private void writeZipHeaderBytes(ZipModel zipModel, OutputStream outputStream, byte[] buff) throws ZipException {
if (buff == null) {
throw new ZipException("invalid buff to write as zip headers");
}
try {
if (outputStream instanceof SplitOutputStream) {
if (((SplitOutputStream)outputStream).checkBuffSizeAndStartNextSplitFile(buff.length)) {
finalizeZipFile(zipModel, outputStream);
return;
}
}
outputStream.write(buff);
} catch (IOException e) {
throw new ZipException(e);
}
}
/**
* Fills the header data in the zip model
* @param zipModel
* @param outputStream
* @throws ZipException
*/
private void processHeaderData(ZipModel zipModel, OutputStream outputStream) throws ZipException {
try {
int currSplitFileCounter = 0;
if (outputStream instanceof SplitOutputStream) {
zipModel.getEndCentralDirRecord().setOffsetOfStartOfCentralDir(
((SplitOutputStream)outputStream).getFilePointer());
currSplitFileCounter = ((SplitOutputStream)outputStream).getCurrSplitFileCounter();
}
if (zipModel.isZip64Format()) {
if (zipModel.getZip64EndCentralDirRecord() == null) {
zipModel.setZip64EndCentralDirRecord(new Zip64EndCentralDirRecord());
}
if (zipModel.getZip64EndCentralDirLocator() == null) {
zipModel.setZip64EndCentralDirLocator(new Zip64EndCentralDirLocator());
}
zipModel.getZip64EndCentralDirLocator().setNoOfDiskStartOfZip64EndOfCentralDirRec(currSplitFileCounter);
zipModel.getZip64EndCentralDirLocator().setTotNumberOfDiscs(currSplitFileCounter + 1);
}
zipModel.getEndCentralDirRecord().setNoOfThisDisk(currSplitFileCounter);
zipModel.getEndCentralDirRecord().setNoOfThisDiskStartOfCentralDir(currSplitFileCounter);
} catch (IOException e) {
throw new ZipException(e);
}
}
/**
* Writes central directory header data to an array list
* @param zipModel
* @param outputStream
* @param headerBytesList
* @return size of central directory
* @throws ZipException
*/
private int writeCentralDirectory(ZipModel zipModel,
OutputStream outputStream, List headerBytesList) throws ZipException {
if (zipModel == null || outputStream == null) {
throw new ZipException("input parameters is null, cannot write central directory");
}
if (zipModel.getCentralDirectory() == null ||
zipModel.getCentralDirectory().getFileHeaders() == null ||
zipModel.getCentralDirectory().getFileHeaders().size() <= 0) {
return 0;
}
int sizeOfCentralDir = 0;
for (int i = 0; i < zipModel.getCentralDirectory().getFileHeaders().size(); i++) {
FileHeader fileHeader = (FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i);
int sizeOfFileHeader = writeFileHeader(zipModel, fileHeader, outputStream, headerBytesList);
sizeOfCentralDir += sizeOfFileHeader;
}
return sizeOfCentralDir;
}
private int writeFileHeader(ZipModel zipModel, FileHeader fileHeader,
OutputStream outputStream, List headerBytesList) throws ZipException {
if (fileHeader == null || outputStream == null) {
throw new ZipException("input parameters is null, cannot write local file header");
}
try {
int sizeOfFileHeader = 0;
byte[] shortByte = new byte[2];
byte[] intByte = new byte[4];
byte[] longByte = new byte[8];
final byte[] emptyShortByte = {0,0};
final byte[] emptyIntByte = {0,0,0,0};
boolean writeZip64FileSize = false;
boolean writeZip64OffsetLocalHeader = false;
Raw.writeIntLittleEndian(intByte, 0, fileHeader.getSignature());
copyByteArrayToArrayList(intByte, headerBytesList);
sizeOfFileHeader += 4;
Raw.writeShortLittleEndian(shortByte, 0, (short)fileHeader.getVersionMadeBy());
copyByteArrayToArrayList(shortByte, headerBytesList);
sizeOfFileHeader += 2;
Raw.writeShortLittleEndian(shortByte, 0, (short)fileHeader.getVersionNeededToExtract());
copyByteArrayToArrayList(shortByte, headerBytesList);
sizeOfFileHeader += 2;
copyByteArrayToArrayList(fileHeader.getGeneralPurposeFlag(), headerBytesList);
sizeOfFileHeader += 2;
Raw.writeShortLittleEndian(shortByte, 0, (short)fileHeader.getCompressionMethod());
copyByteArrayToArrayList(shortByte, headerBytesList);
sizeOfFileHeader += 2;
int dateTime = fileHeader.getLastModFileTime();
Raw.writeIntLittleEndian(intByte, 0, dateTime);
copyByteArrayToArrayList(intByte, headerBytesList);
sizeOfFileHeader += 4;
Raw.writeIntLittleEndian(intByte, 0, (int)(fileHeader.getCrc32()));
copyByteArrayToArrayList(intByte, headerBytesList);
sizeOfFileHeader += 4;
if (fileHeader.getCompressedSize() >= InternalZipConstants.ZIP_64_LIMIT ||
fileHeader.getUncompressedSize() + ZIP64_EXTRA_BUF >= InternalZipConstants.ZIP_64_LIMIT) {
Raw.writeLongLittleEndian(longByte, 0, InternalZipConstants.ZIP_64_LIMIT);
System.arraycopy(longByte, 0, intByte, 0, 4);
copyByteArrayToArrayList(intByte, headerBytesList);
sizeOfFileHeader += 4;
copyByteArrayToArrayList(intByte, headerBytesList);
sizeOfFileHeader += 4;
writeZip64FileSize = true;
} else {
Raw.writeLongLittleEndian(longByte, 0, fileHeader.getCompressedSize());
System.arraycopy(longByte, 0, intByte, 0, 4);
// Raw.writeIntLittleEndian(intByte, 0, (int)fileHeader.getCompressedSize());
copyByteArrayToArrayList(intByte, headerBytesList);
sizeOfFileHeader += 4;
Raw.writeLongLittleEndian(longByte, 0, fileHeader.getUncompressedSize());
System.arraycopy(longByte, 0, intByte, 0, 4);
// Raw.writeIntLittleEndian(intByte, 0, (int)fileHeader.getUncompressedSize());
copyByteArrayToArrayList(intByte, headerBytesList);
sizeOfFileHeader += 4;
}
Raw.writeShortLittleEndian(shortByte, 0, (short)fileHeader.getFileNameLength());
copyByteArrayToArrayList(shortByte, headerBytesList);
sizeOfFileHeader += 2;
//Compute offset bytes before extra field is written for Zip64 compatibility
//NOTE: this data is not written now, but written at a later point
byte[] offsetLocalHeaderBytes = new byte[4];
if (fileHeader.getOffsetLocalHeader() > InternalZipConstants.ZIP_64_LIMIT) {
Raw.writeLongLittleEndian(longByte, 0, InternalZipConstants.ZIP_64_LIMIT);
System.arraycopy(longByte, 0, offsetLocalHeaderBytes, 0, 4);
writeZip64OffsetLocalHeader = true;
} else {
Raw.writeLongLittleEndian(longByte, 0, fileHeader.getOffsetLocalHeader());
System.arraycopy(longByte, 0, offsetLocalHeaderBytes, 0, 4);
}
// extra field length
int extraFieldLength = 0;
if (writeZip64FileSize || writeZip64OffsetLocalHeader) {
extraFieldLength += 4;
if (writeZip64FileSize)
extraFieldLength += 16;
if (writeZip64OffsetLocalHeader)
extraFieldLength += 8;
}
if (fileHeader.getAesExtraDataRecord() != null) {
extraFieldLength += 11;
}
Raw.writeShortLittleEndian(shortByte, 0, (short)(extraFieldLength));
copyByteArrayToArrayList(shortByte, headerBytesList);
sizeOfFileHeader += 2;
//Skip file comment length for now
copyByteArrayToArrayList(emptyShortByte, headerBytesList);
sizeOfFileHeader += 2;
//Skip disk number start for now
Raw.writeShortLittleEndian(shortByte, 0, (short)(fileHeader.getDiskNumberStart()));
copyByteArrayToArrayList(shortByte, headerBytesList);
sizeOfFileHeader += 2;
//Skip internal file attributes for now
copyByteArrayToArrayList(emptyShortByte, headerBytesList);
sizeOfFileHeader += 2;
//External file attributes
if (fileHeader.getExternalFileAttr() != null) {
copyByteArrayToArrayList(fileHeader.getExternalFileAttr(), headerBytesList);
} else {
copyByteArrayToArrayList(emptyIntByte, headerBytesList);
}
sizeOfFileHeader += 4;
//offset local header
//this data is computed above
copyByteArrayToArrayList(offsetLocalHeaderBytes, headerBytesList);
sizeOfFileHeader += 4;
if (Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getFileNameCharset())) {
byte[] fileNameBytes = fileHeader.getFileName().getBytes(zipModel.getFileNameCharset());
copyByteArrayToArrayList(fileNameBytes, headerBytesList);
sizeOfFileHeader += fileNameBytes.length;
} else {
copyByteArrayToArrayList(Zip4jUtil.convertCharset(fileHeader.getFileName()), headerBytesList);
sizeOfFileHeader += Zip4jUtil.getEncodedStringLength(fileHeader.getFileName());
}
if (writeZip64FileSize || writeZip64OffsetLocalHeader) {
zipModel.setZip64Format(true);
//Zip64 header
Raw.writeShortLittleEndian(shortByte, 0, (short)InternalZipConstants.EXTRAFIELDZIP64LENGTH);
copyByteArrayToArrayList(shortByte, headerBytesList);
sizeOfFileHeader += 2;
//Zip64 extra data record size
int dataSize = 0;
if (writeZip64FileSize) {
dataSize += 16;
}
if (writeZip64OffsetLocalHeader) {
dataSize += 8;
}
Raw.writeShortLittleEndian(shortByte, 0, (short)dataSize);
copyByteArrayToArrayList(shortByte, headerBytesList);
sizeOfFileHeader += 2;
if (writeZip64FileSize) {
Raw.writeLongLittleEndian(longByte, 0, fileHeader.getUncompressedSize());
copyByteArrayToArrayList(longByte, headerBytesList);
sizeOfFileHeader += 8;
Raw.writeLongLittleEndian(longByte, 0, fileHeader.getCompressedSize());
copyByteArrayToArrayList(longByte, headerBytesList);
sizeOfFileHeader += 8;
}
if (writeZip64OffsetLocalHeader) {
Raw.writeLongLittleEndian(longByte, 0, fileHeader.getOffsetLocalHeader());
copyByteArrayToArrayList(longByte, headerBytesList);
sizeOfFileHeader += 8;
}
}
if (fileHeader.getAesExtraDataRecord() != null) {
AESExtraDataRecord aesExtraDataRecord = fileHeader.getAesExtraDataRecord();
Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getSignature());
copyByteArrayToArrayList(shortByte, headerBytesList);
Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getDataSize());
copyByteArrayToArrayList(shortByte, headerBytesList);
Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getVersionNumber());
copyByteArrayToArrayList(shortByte, headerBytesList);
copyByteArrayToArrayList(aesExtraDataRecord.getVendorID().getBytes(), headerBytesList);
byte[] aesStrengthBytes = new byte[1];
aesStrengthBytes[0] = (byte)aesExtraDataRecord.getAesStrength();
copyByteArrayToArrayList(aesStrengthBytes, headerBytesList);
Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getCompressionMethod());
copyByteArrayToArrayList(shortByte, headerBytesList);
sizeOfFileHeader += 11;
}
// outputStream.write(byteArrayListToByteArray(headerBytesList));
return sizeOfFileHeader;
} catch (Exception e) {
throw new ZipException(e);
}
}
private void writeZip64EndOfCentralDirectoryRecord(ZipModel zipModel,
OutputStream outputStream, int sizeOfCentralDir,
long offsetCentralDir, List headerBytesList) throws ZipException {
if (zipModel == null || outputStream == null) {
throw new ZipException("zip model or output stream is null, cannot write zip64 end of central directory record");
}
try {
byte[] shortByte = new byte[2];
byte[] emptyShortByte = {0,0};
byte[] intByte = new byte[4];
byte[] longByte = new byte[8];
//zip64 end of central dir signature
Raw.writeIntLittleEndian(intByte, 0, (int)InternalZipConstants.ZIP64ENDCENDIRREC);
copyByteArrayToArrayList(intByte, headerBytesList);
//size of zip64 end of central directory record
Raw.writeLongLittleEndian(longByte, 0, (long)44);
copyByteArrayToArrayList(longByte, headerBytesList);
//version made by
//version needed to extract
if (zipModel.getCentralDirectory() != null &&
zipModel.getCentralDirectory().getFileHeaders() != null &&
zipModel.getCentralDirectory().getFileHeaders().size() > 0) {
Raw.writeShortLittleEndian(shortByte, 0,
(short)((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(0)).getVersionMadeBy());
copyByteArrayToArrayList(shortByte, headerBytesList);
Raw.writeShortLittleEndian(shortByte, 0,
(short)((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(0)).getVersionNeededToExtract());
copyByteArrayToArrayList(shortByte, headerBytesList);
} else {
copyByteArrayToArrayList(emptyShortByte, headerBytesList);
copyByteArrayToArrayList(emptyShortByte, headerBytesList);
}
//number of this disk
Raw.writeIntLittleEndian(intByte, 0, zipModel.getEndCentralDirRecord().getNoOfThisDisk());
copyByteArrayToArrayList(intByte, headerBytesList);
//number of the disk with start of central directory
Raw.writeIntLittleEndian(intByte, 0, zipModel.getEndCentralDirRecord().getNoOfThisDiskStartOfCentralDir());
copyByteArrayToArrayList(intByte, headerBytesList);
//total number of entries in the central directory on this disk
int numEntries = 0;
int numEntriesOnThisDisk = 0;
if (zipModel.getCentralDirectory() == null ||
zipModel.getCentralDirectory().getFileHeaders() == null) {
throw new ZipException("invalid central directory/file headers, " +
"cannot write end of central directory record");
} else {
numEntries = zipModel.getCentralDirectory().getFileHeaders().size();
if (zipModel.isSplitArchive()) {
countNumberOfFileHeaderEntriesOnDisk(zipModel.getCentralDirectory().getFileHeaders(),
zipModel.getEndCentralDirRecord().getNoOfThisDisk());
} else {
numEntriesOnThisDisk = numEntries;
}
}
Raw.writeLongLittleEndian(longByte, 0, numEntriesOnThisDisk);
copyByteArrayToArrayList(longByte, headerBytesList);
//Total number of entries in central directory
Raw.writeLongLittleEndian(longByte, 0, numEntries);
copyByteArrayToArrayList(longByte, headerBytesList);
//Size of central directory
Raw.writeLongLittleEndian(longByte, 0, sizeOfCentralDir);
copyByteArrayToArrayList(longByte, headerBytesList);
//offset of start of central directory with respect to the starting disk number
Raw.writeLongLittleEndian(longByte, 0, offsetCentralDir);
copyByteArrayToArrayList(longByte, headerBytesList);
} catch (ZipException zipException) {
throw zipException;
} catch (Exception e) {
throw new ZipException(e);
}
}
private void writeZip64EndOfCentralDirectoryLocator(ZipModel zipModel,
OutputStream outputStream, List headerBytesList) throws ZipException {
if (zipModel == null || outputStream == null) {
throw new ZipException("zip model or output stream is null, cannot write zip64 end of central directory locator");
}
try {
byte[] intByte = new byte[4];
byte[] longByte = new byte[8];
//zip64 end of central dir locator signature
Raw.writeIntLittleEndian(intByte, 0, (int)InternalZipConstants.ZIP64ENDCENDIRLOC);
copyByteArrayToArrayList(intByte, headerBytesList);
//number of the disk with the start of the zip64 end of central directory
Raw.writeIntLittleEndian(intByte, 0, zipModel.getZip64EndCentralDirLocator().getNoOfDiskStartOfZip64EndOfCentralDirRec());
copyByteArrayToArrayList(intByte, headerBytesList);
//relative offset of the zip64 end of central directory record
Raw.writeLongLittleEndian(longByte, 0, zipModel.getZip64EndCentralDirLocator().getOffsetZip64EndOfCentralDirRec());
copyByteArrayToArrayList(longByte, headerBytesList);
//total number of disks
Raw.writeIntLittleEndian(intByte, 0, zipModel.getZip64EndCentralDirLocator().getTotNumberOfDiscs());
copyByteArrayToArrayList(intByte, headerBytesList);
} catch (ZipException zipException) {
throw zipException;
} catch (Exception e) {
throw new ZipException(e);
}
}
private void writeEndOfCentralDirectoryRecord(ZipModel zipModel,
OutputStream outputStream,
int sizeOfCentralDir,
long offsetCentralDir,
List headrBytesList) throws ZipException {
if (zipModel == null || outputStream == null) {
throw new ZipException("zip model or output stream is null, cannot write end of central directory record");
}
try {
byte[] shortByte = new byte[2];
byte[] intByte = new byte[4];
byte[] longByte = new byte[8];
//End of central directory signature
Raw.writeIntLittleEndian(intByte, 0, (int)zipModel.getEndCentralDirRecord().getSignature());
copyByteArrayToArrayList(intByte, headrBytesList);
//number of this disk
Raw.writeShortLittleEndian(shortByte, 0, (short)(zipModel.getEndCentralDirRecord().getNoOfThisDisk()));
copyByteArrayToArrayList(shortByte, headrBytesList);
//number of the disk with start of central directory
Raw.writeShortLittleEndian(shortByte, 0, (short)(zipModel.getEndCentralDirRecord().getNoOfThisDiskStartOfCentralDir()));
copyByteArrayToArrayList(shortByte, headrBytesList);
//Total number of entries in central directory on this disk
int numEntries = 0;
int numEntriesOnThisDisk = 0;
if (zipModel.getCentralDirectory() == null ||
zipModel.getCentralDirectory().getFileHeaders() == null) {
throw new ZipException("invalid central directory/file headers, " +
"cannot write end of central directory record");
} else {
numEntries = zipModel.getCentralDirectory().getFileHeaders().size();
if (zipModel.isSplitArchive()) {
numEntriesOnThisDisk = countNumberOfFileHeaderEntriesOnDisk(zipModel.getCentralDirectory().getFileHeaders(),
zipModel.getEndCentralDirRecord().getNoOfThisDisk());
} else {
numEntriesOnThisDisk = numEntries;
}
}
Raw.writeShortLittleEndian(shortByte, 0, (short)numEntriesOnThisDisk);
copyByteArrayToArrayList(shortByte, headrBytesList);
//Total number of entries in central directory
Raw.writeShortLittleEndian(shortByte, 0, (short)numEntries);
copyByteArrayToArrayList(shortByte, headrBytesList);
//Size of central directory
Raw.writeIntLittleEndian(intByte, 0, sizeOfCentralDir);
copyByteArrayToArrayList(intByte, headrBytesList);
//Offset central directory
if (offsetCentralDir > InternalZipConstants.ZIP_64_LIMIT) {
Raw.writeLongLittleEndian(longByte, 0, InternalZipConstants.ZIP_64_LIMIT);
System.arraycopy(longByte, 0, intByte, 0, 4);
copyByteArrayToArrayList(intByte, headrBytesList);
} else {
Raw.writeLongLittleEndian(longByte, 0, offsetCentralDir);
System.arraycopy(longByte, 0, intByte, 0, 4);
// Raw.writeIntLittleEndian(intByte, 0, (int)offsetCentralDir);
copyByteArrayToArrayList(intByte, headrBytesList);
}
//Zip File comment length
int commentLength = 0;
if (zipModel.getEndCentralDirRecord().getComment() != null) {
commentLength = zipModel.getEndCentralDirRecord().getCommentLength();
}
Raw.writeShortLittleEndian(shortByte, 0, (short)commentLength);
copyByteArrayToArrayList(shortByte, headrBytesList);
//Comment
if (commentLength > 0) {
copyByteArrayToArrayList(zipModel.getEndCentralDirRecord().getCommentBytes(), headrBytesList);
}
} catch (Exception e) {
throw new ZipException(e);
}
}
public void updateLocalFileHeader(LocalFileHeader localFileHeader, long offset,
int toUpdate, ZipModel zipModel, byte[] bytesToWrite, int noOfDisk, SplitOutputStream outputStream) throws ZipException {
if (localFileHeader == null || offset < 0 || zipModel == null) {
throw new ZipException("invalid input parameters, cannot update local file header");
}
try {
boolean closeFlag = false;
SplitOutputStream currOutputStream = null;
if (noOfDisk != outputStream.getCurrSplitFileCounter()) {
File zipFile = new File(zipModel.getZipFile());
String parentFile = zipFile.getParent();
String fileNameWithoutExt = Zip4jUtil.getZipFileNameWithoutExt(zipFile.getName());
String fileName = parentFile + System.getProperty("file.separator");
if (noOfDisk < 9) {
fileName += fileNameWithoutExt + ".z0" + (noOfDisk + 1);
} else {
fileName += fileNameWithoutExt + ".z" + (noOfDisk + 1);
}
currOutputStream = new SplitOutputStream(new File(fileName));
closeFlag = true;
} else {
currOutputStream = outputStream;
}
long currOffset = currOutputStream.getFilePointer();
switch (toUpdate) {
case InternalZipConstants.UPDATE_LFH_CRC:
currOutputStream.seek(offset + toUpdate);
currOutputStream.write(bytesToWrite);
break;
case InternalZipConstants.UPDATE_LFH_COMP_SIZE:
case InternalZipConstants.UPDATE_LFH_UNCOMP_SIZE:
updateCompressedSizeInLocalFileHeader(currOutputStream, localFileHeader,
offset, toUpdate, bytesToWrite, zipModel.isZip64Format());
break;
default:
break;
}
if (closeFlag) {
currOutputStream.close();
} else {
outputStream.seek(currOffset);
}
} catch (Exception e) {
throw new ZipException(e);
}
}
private void updateCompressedSizeInLocalFileHeader(SplitOutputStream outputStream, LocalFileHeader localFileHeader,
long offset, long toUpdate, byte[] bytesToWrite, boolean isZip64Format) throws ZipException {
if (outputStream == null) {
throw new ZipException("invalid output stream, cannot update compressed size for local file header");
}
try {
if (localFileHeader.isWriteComprSizeInZip64ExtraRecord()) {
if (bytesToWrite.length != 8) {
throw new ZipException("attempting to write a non 8-byte compressed size block for a zip64 file");
}
//4 - compressed size
//4 - uncomprssed size
//2 - file name length
//2 - extra field length
//file name length
//2 - Zip64 signature
//2 - size of zip64 data
//8 - crc
//8 - compressed size
//8 - uncompressed size
long zip64CompressedSizeOffset = offset + toUpdate + 4 + 4 + 2 + 2 + localFileHeader.getFileNameLength() + 2 + 2 + 8;
if (toUpdate == InternalZipConstants.UPDATE_LFH_UNCOMP_SIZE) {
zip64CompressedSizeOffset += 8;
}
outputStream.seek(zip64CompressedSizeOffset);
outputStream.write(bytesToWrite);
} else {
outputStream.seek(offset + toUpdate);
outputStream.write(bytesToWrite);
}
} catch (IOException e) {
throw new ZipException(e);
}
}
private void copyByteArrayToArrayList(byte[] byteArray, List arrayList) throws ZipException {
if (arrayList == null || byteArray == null) {
throw new ZipException("one of the input parameters is null, cannot copy byte array to array list");
}
for (int i = 0; i < byteArray.length; i++) {
arrayList.add(Byte.toString(byteArray[i]));
}
}
private byte[] byteArrayListToByteArray(List arrayList) throws ZipException {
if (arrayList == null) {
throw new ZipException("input byte array list is null, cannot conver to byte array");
}
if (arrayList.size() <= 0) {
return null;
}
byte[] retBytes = new byte[arrayList.size()];
for (int i = 0; i < arrayList.size(); i++) {
retBytes[i] = Byte.parseByte((String)arrayList.get(i));
}
return retBytes;
}
private int countNumberOfFileHeaderEntriesOnDisk(ArrayList fileHeaders,
int numOfDisk) throws ZipException {
if (fileHeaders == null) {
throw new ZipException("file headers are null, cannot calculate number of entries on this disk");
}
int noEntries = 0;
for (int i = 0; i < fileHeaders.size(); i++) {
FileHeader fileHeader = (FileHeader)fileHeaders.get(i);
if (fileHeader.getDiskNumberStart() == numOfDisk) {
noEntries++;
}
}
return noEntries;
}
}