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