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