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