/*
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved.
*/
package zip4cj.io.outputstream
public class HeaderWriter {
private static let ZIP64_EXTRA_DATA_RECORD_SIZE_LFH: Int32 = 16
private static let ZIP64_EXTRA_DATA_RECORD_SIZE_FH: Int32 = 28
private static let AES_EXTRA_DATA_RECORD_SIZE: Int32 = 11
private let rawIO = RawIO()
private let longBuff = Array<Byte>(8, repeat: 0)
private let intBuff = Array<Byte>(4, repeat: 0)
public func writeLocalFileHeader(
zipModel: ZipModel,
localFileHeader: LocalFileHeader,
outputStream: OutputStream
) {
let byteArrayOutputStream = ByteBuffer()
rawIO.writeIntLittleEndian(byteArrayOutputStream, Int32(localFileHeader.getSignature().getOrThrow().getValue()))
rawIO.writeShortLittleEndian(byteArrayOutputStream, localFileHeader.getVersionNeededToExtract())
if (let Some(v) <- localFileHeader.getGeneralPurposeFlag()) {
byteArrayOutputStream.write(v)
} else {
throw NoneValueException("cannot read the array length because array is None")
}
rawIO.writeShortLittleEndian(
byteArrayOutputStream,
localFileHeader.getCompressionMethod().getOrThrow().getCode()
)
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getLastModifiedTime())
byteArrayOutputStream.write(longBuff[0..4])
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getCrc())
byteArrayOutputStream.write(longBuff[0..4])
var writeZip64Header = localFileHeader.getCompressedSize() >= InternalZipConstants.ZIP_64_SIZE_LIMIT ||
localFileHeader.getUncompressedSize() >= InternalZipConstants.ZIP_64_SIZE_LIMIT
if (writeZip64Header) {
rawIO.writeLongLittleEndian(longBuff, 0, InternalZipConstants.ZIP_64_SIZE_LIMIT)
//Set the uncompressed size to ZipConstants.InternalZipConstants.ZIP_64_SIZE_LIMIT as
//these values will be stored in Zip64 extra record
byteArrayOutputStream.write(longBuff[0..4])
byteArrayOutputStream.write(longBuff[0..4])
zipModel.setZip64Format(true)
localFileHeader.setWriteCompressedSizeInZip64ExtraRecord(true)
} else {
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getCompressedSize())
byteArrayOutputStream.write(longBuff[0..4])
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getUncompressedSize())
byteArrayOutputStream.write(longBuff[0..4])
localFileHeader.setWriteCompressedSizeInZip64ExtraRecord(false)
}
var fileNameBytes = InternalZipConstants.NULL_BYTE_ARRAY
if (Zip4cjUtil.isStringNotNullAndNotEmpty(localFileHeader.getFileName())) {
fileNameBytes = HeaderUtil.getBytesFromString(localFileHeader.getFileName().getOrThrow())
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(fileNameBytes.size))
var extraFieldLength: Int32 = 0
if (writeZip64Header) {
extraFieldLength += Int32(ZIP64_EXTRA_DATA_RECORD_SIZE_LFH) + 4 // 4 for signature + size of record
}
if (localFileHeader.getAesExtraDataRecord().isSome()) {
extraFieldLength += Int32(AES_EXTRA_DATA_RECORD_SIZE)
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, extraFieldLength)
if (fileNameBytes.size > 0) {
byteArrayOutputStream.write(fileNameBytes)
}
//Zip64 should be the first extra data record that should be written
//This is NOT according to any specification but if this is changed
//corresponding logic for updateLocalFileHeader for compressed size
//has to be modified as well
if (writeZip64Header) {
rawIO.writeShortLittleEndian(
byteArrayOutputStream,
Int32(HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue())
)
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(ZIP64_EXTRA_DATA_RECORD_SIZE_LFH))
rawIO.writeLongLittleEndian(byteArrayOutputStream, localFileHeader.getUncompressedSize())
rawIO.writeLongLittleEndian(byteArrayOutputStream, localFileHeader.getCompressedSize())
}
if (let Some(aesExtraDataRecord) <- localFileHeader.getAesExtraDataRecord()) {
rawIO.writeShortLittleEndian(
byteArrayOutputStream,
Int32(aesExtraDataRecord.getSignature().getOrThrow().getValue())
)
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(aesExtraDataRecord.getDataSize()))
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getAesVersion().getVersionNumber())
byteArrayOutputStream.write(HeaderUtil.getBytesFromString(aesExtraDataRecord.getVendorID()))
var aesStrengthBytes = Array<Byte>(1, repeat: 0)
aesStrengthBytes[0] = UInt8(aesExtraDataRecord.getAesKeyStrength().getRawCode())
byteArrayOutputStream.write(aesStrengthBytes)
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getCompressionMethod().getCode())
}
let bytes = byteArrayOutputStream.bytes()
outputStream.write(bytes)
}
@OverflowWrapping
public func writeExtendedLocalHeader(localFileHeader: ?LocalFileHeader, outputStream: OutputStream) {
if (localFileHeader.isNone()) {
throw ZipException("input parameters is None, cannot write extended local header")
}
let byteArrayOutputStream = ByteBuffer()
rawIO.writeIntLittleEndian(byteArrayOutputStream, Int32(HeaderSignature.EXTRA_DATA_RECORD.getValue()))
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getOrThrow().getCrc())
byteArrayOutputStream.write(longBuff[0..4])
if (localFileHeader.getOrThrow().isWriteCompressedSizeInZip64ExtraRecord()) {
rawIO.writeLongLittleEndian(byteArrayOutputStream, localFileHeader.getOrThrow().getCompressedSize())
rawIO.writeLongLittleEndian(byteArrayOutputStream, localFileHeader.getOrThrow().getUncompressedSize())
} else {
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getOrThrow().getCompressedSize())
byteArrayOutputStream.write(longBuff[0..4])
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getOrThrow().getUncompressedSize())
byteArrayOutputStream.write(longBuff[0..4])
}
outputStream.write(byteArrayOutputStream.bytes())
}
public func finalizeZipFile(zipModel: ZipModel, outputStream: OutputStream): Unit {
let byteArrayOutputStream = ByteBuffer()
processHeaderData(zipModel, outputStream)
let offsetCentralDir = getOffsetOfCentralDirectory(zipModel)
writeCentralDirectory(zipModel, byteArrayOutputStream, rawIO)
let sizeOfCentralDir = byteArrayOutputStream.length
if (zipModel.isZip64Format() || offsetCentralDir >= InternalZipConstants.ZIP_64_SIZE_LIMIT ||
zipModel.getCentralDirectory().getOrThrow().getFileHeaders().size >= InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT) {
if (zipModel.getZip64EndOfCentralDirectoryLocator().isNone()) {
zipModel.setZip64EndOfCentralDirectoryLocator(Zip64EndOfCentralDirectoryLocator())
}
zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow().setOffsetZip64EndOfCentralDirectoryRecord(
offsetCentralDir + sizeOfCentralDir)
if (isSplitZipFile(outputStream)) {
var currentSplitFileCounter = getCurrentSplitFileCounter(outputStream)
zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow().
setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(currentSplitFileCounter)
zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow().setTotalNumberOfDiscs(
currentSplitFileCounter + 1)
} else {
zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow().
setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(0)
zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow().setTotalNumberOfDiscs(1)
}
var zip64EndOfCentralDirectoryRecord = buildZip64EndOfCentralDirectoryRecord(zipModel,
Int64(sizeOfCentralDir), offsetCentralDir)
zipModel.setZip64EndOfCentralDirectoryRecord(zip64EndOfCentralDirectoryRecord)
writeZip64EndOfCentralDirectoryRecord(zip64EndOfCentralDirectoryRecord, byteArrayOutputStream, rawIO)
writeZip64EndOfCentralDirectoryLocator(zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow(),
byteArrayOutputStream, rawIO)
}
writeEndOfCentralDirectoryRecord(zipModel, Int32(sizeOfCentralDir), offsetCentralDir, byteArrayOutputStream,
rawIO)
writeZipHeaderBytes(zipModel, outputStream, byteArrayOutputStream.bytes())
}
public func finalizeZipFileWithoutValidations(zipModel: ZipModel, outputStream: OutputStream) {
let byteArrayOutputStream: ByteBuffer = ByteBuffer()
let offsetCentralDir = getOffsetOfCentralDirectory(zipModel)
writeCentralDirectory(zipModel, byteArrayOutputStream, rawIO)
let sizeOfCentralDir = byteArrayOutputStream.length
if (zipModel.isZip64Format() || offsetCentralDir >= InternalZipConstants.ZIP_64_SIZE_LIMIT ||
zipModel.getCentralDirectory().getOrThrow().getFileHeaders().size >= InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT) {
zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow().setOffsetZip64EndOfCentralDirectoryRecord(
offsetCentralDir + sizeOfCentralDir)
var zip64EndOfCentralDirectoryRecord: Zip64EndOfCentralDirectoryRecord = buildZip64EndOfCentralDirectoryRecord(
zipModel, sizeOfCentralDir, offsetCentralDir)
zipModel.setZip64EndOfCentralDirectoryRecord(zip64EndOfCentralDirectoryRecord)
writeZip64EndOfCentralDirectoryRecord(zip64EndOfCentralDirectoryRecord, byteArrayOutputStream, rawIO)
writeZip64EndOfCentralDirectoryLocator(zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow(),
byteArrayOutputStream, rawIO)
}
writeEndOfCentralDirectoryRecord(zipModel, Int32(sizeOfCentralDir), offsetCentralDir, byteArrayOutputStream,
rawIO)
writeZipHeaderBytes(zipModel, outputStream, byteArrayOutputStream.bytes())
}
public func updateLocalFileHeader(fileHeader: FileHeader, zipModel: ZipModel, outputStream: SplitOutputStream) {
var closeFlag = false
var currOutputStream: SplitOutputStream
if (fileHeader.getDiskNumberStart() != Int64(outputStream.getCurrentSplitFileCounter())) {
var parentFile = zipModel.getZipFile().parent
var fileNameWithoutExt = FileUtils.getZipFileNameWithoutExtension(
zipModel.getZipFile().fileName)
var fileName = parentFile.toString() + Path.Separator
if (fileHeader.getDiskNumberStart() < 9) {
fileName += fileNameWithoutExt + ".z0${(fileHeader.getDiskNumberStart() + 1)}"
} else {
fileName += fileNameWithoutExt + ".z${(fileHeader.getDiskNumberStart() + 1)}"
}
currOutputStream = SplitOutputStream(Path(fileName))
closeFlag = true
} else {
currOutputStream = outputStream
}
var currOffset = currOutputStream.getFilePointer()
currOutputStream.seek(fileHeader.getOffsetLocalHeader() + Int64(InternalZipConstants.UPDATE_LFH_CRC))
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getCrc())
currOutputStream.write(longBuff[0..4])
updateFileSizesInLocalFileHeader(currOutputStream, fileHeader)
if (closeFlag) {
currOutputStream.close()
} else {
outputStream.seek(currOffset)
}
}
private func updateFileSizesInLocalFileHeader(outputStream: SplitOutputStream, fileHeader: FileHeader) {
if (fileHeader.getUncompressedSize() >= InternalZipConstants.ZIP_64_SIZE_LIMIT) {
rawIO.writeLongLittleEndian(longBuff, 0, InternalZipConstants.ZIP_64_SIZE_LIMIT)
outputStream.write(longBuff[0..4])
outputStream.write(longBuff[0..4])
//2 - file name length
//2 - extra field length
//variable - file name which can be determined by fileNameLength
//2 - Zip64 signature
//2 - size of zip64 data
//8 - uncompressed size
//8 - compressed size
var zip64CompressedSizeOffset = 2 + 2 + fileHeader.getFileNameLength() + 2 + 2
if (outputStream.skipBytes(zip64CompressedSizeOffset) != zip64CompressedSizeOffset) {
throw ZipException("Unable to skip ${zip64CompressedSizeOffset} bytes to update LFH")
}
rawIO.writeLongLittleEndian(outputStream, fileHeader.getUncompressedSize())
rawIO.writeLongLittleEndian(outputStream, fileHeader.getCompressedSize())
} else {
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getCompressedSize())
outputStream.write(longBuff[0..4])
}
}
private func isSplitZipFile(outputStream: OutputStream): Bool {
if (let Some(output) <- (outputStream as SplitOutputStream)) {
return output.isSplitZipFile()
} else if (let Some(output) <- (outputStream as CountingOutputStream)) {
return output.isSplitZipFile()
}
return false
}
private func getCurrentSplitFileCounter(outputStream: OutputStream): Int32 {
if (let Some(output) <-( outputStream as SplitOutputStream)) {
return output.getCurrentSplitFileCounter()
}
if (let Some(output) <- (outputStream as CountingOutputStream)) {
return output.getCurrentSplitFileCounter()
}
return -1
}
private func writeZipHeaderBytes(
zipModel: ZipModel,
outputStream: OutputStream,
buff: Array<Byte>
) {
if (let Some(output) <- (outputStream as CountingOutputStream)) {
if (output.checkBuffSizeAndStartNextSplitFile(Int32(buff.size))) {
finalizeZipFile(zipModel, output)
return
}
}
outputStream.write(buff)
}
private func processHeaderData(zipModel: ZipModel, outputStream: OutputStream) {
var currentSplitFileCounter: Int32 = 0
if (let Some(output) <- (outputStream as OutputStreamWithSplitZipSupport)) {
zipModel.getEndOfCentralDirectoryRecord().setOffsetOfStartOfCentralDirectory(output.getFilePointer())
currentSplitFileCounter = output.getCurrentSplitFileCounter()
}
if (zipModel.isZip64Format()) {
zipModel.getZip64EndOfCentralDirectoryRecord().setOffsetStartCentralDirectoryWRTStartDiskNumber(
zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory())
zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow().
setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(currentSplitFileCounter)
zipModel.getZip64EndOfCentralDirectoryLocator().getOrThrow().setTotalNumberOfDiscs(
currentSplitFileCounter + 1)
}
zipModel.getEndOfCentralDirectoryRecord().setNumberOfThisDisk(currentSplitFileCounter)
zipModel.getEndOfCentralDirectoryRecord().setNumberOfThisDiskStartOfCentralDir(currentSplitFileCounter)
}
private func writeCentralDirectory(
zipModel: ZipModel,
byteArrayOutputStream: ByteBuffer,
rawIO: RawIO
) {
if (zipModel.getCentralDirectory().getOrThrow().getFileHeaders().size <= 0) {
return
}
let fileHeaders = zipModel.getCentralDirectory().getOrThrow().getFileHeaders()
for (i in 0..fileHeaders.size) {
writeFileHeader(zipModel, fileHeaders[i], byteArrayOutputStream, rawIO)
}
}
private func writeFileHeader(
zipModel: ZipModel,
fileHeader: FileHeader,
byteArrayOutputStream: ByteBuffer,
rawIO: RawIO
) {
let emptyShortByte = Array<Byte>(2, repeat: 0)
var writeZip64ExtendedInfo = this.isZip64Entry(fileHeader)
rawIO.writeIntLittleEndian(byteArrayOutputStream, Int32(fileHeader.getSignature().getOrThrow().getValue()))
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileHeader.getVersionMadeBy())
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileHeader.getVersionNeededToExtract())
byteArrayOutputStream.write(fileHeader.getGeneralPurposeFlag().getOrThrow())
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileHeader.getCompressionMethod().getOrThrow().getCode())
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getLastModifiedTime())
byteArrayOutputStream.write(longBuff[0..4])
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getCrc())
byteArrayOutputStream.write(longBuff[0..4])
if (writeZip64ExtendedInfo) {
rawIO.writeLongLittleEndian(longBuff, 0, InternalZipConstants.ZIP_64_SIZE_LIMIT)
byteArrayOutputStream.write(longBuff[0..4])
byteArrayOutputStream.write(longBuff[0..4])
zipModel.setZip64Format(true)
} else {
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getCompressedSize())
byteArrayOutputStream.write(longBuff[0..4])
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getUncompressedSize())
byteArrayOutputStream.write(longBuff[0..4])
}
var fileNameBytes = Array<Byte>(1, repeat: 0)
if (Zip4cjUtil.isStringNotNullAndNotEmpty(fileHeader.getFileName())) {
fileNameBytes = HeaderUtil.getBytesFromString(fileHeader.getFileName().getOrThrow())
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(fileNameBytes.size))
//Compute offset bytes before extra field is written for Zip64 compatibility
//NOTE: this data is not written now, but written at a later point
var offsetLocalHeaderBytes = Array<Byte>(4, repeat: 0)
if (writeZip64ExtendedInfo) {
rawIO.writeLongLittleEndian(longBuff, 0, InternalZipConstants.ZIP_64_SIZE_LIMIT)
ArrayCopy(longBuff, 0, offsetLocalHeaderBytes, 0, 4)
} else {
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getOffsetLocalHeader())
ArrayCopy(longBuff, 0, offsetLocalHeaderBytes, 0, 4)
}
var extraFieldLength = calculateExtraDataRecordsSize(fileHeader, writeZip64ExtendedInfo)
rawIO.writeShortLittleEndian(byteArrayOutputStream, extraFieldLength)
var fileComment = fileHeader.getFileComment()
var fileCommentBytes = InternalZipConstants.NULL_BYTE_ARRAY
if (Zip4cjUtil.isStringNotNullAndNotEmpty(fileComment)) {
fileCommentBytes = HeaderUtil.getBytesFromString(fileComment.getOrThrow())
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(fileCommentBytes.size))
if (writeZip64ExtendedInfo) {
rawIO.writeIntLittleEndian(intBuff, 0, Int32(InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT))
byteArrayOutputStream.write(intBuff[0..2])
} else {
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(fileHeader.getDiskNumberStart()))
}
byteArrayOutputStream.write(emptyShortByte)
//External file attributes
byteArrayOutputStream.write(fileHeader.getExternalFileAttributes().getOrThrow())
//offset local header - this data is computed above
byteArrayOutputStream.write(offsetLocalHeaderBytes)
if (fileNameBytes.size > 0) {
byteArrayOutputStream.write(fileNameBytes)
}
if (writeZip64ExtendedInfo) {
zipModel.setZip64Format(true)
//Zip64 header
rawIO.writeShortLittleEndian(
byteArrayOutputStream,
Int32(HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue())
)
//size of data
rawIO.writeShortLittleEndian(byteArrayOutputStream, ZIP64_EXTRA_DATA_RECORD_SIZE_FH)
rawIO.writeLongLittleEndian(byteArrayOutputStream, fileHeader.getUncompressedSize())
rawIO.writeLongLittleEndian(byteArrayOutputStream, fileHeader.getCompressedSize())
rawIO.writeLongLittleEndian(byteArrayOutputStream, fileHeader.getOffsetLocalHeader())
rawIO.writeIntLittleEndian(byteArrayOutputStream, Int32(fileHeader.getDiskNumberStart()))
}
if (let Some(aesExtraDataRecord) <- fileHeader.getAesExtraDataRecord()) {
rawIO.writeShortLittleEndian(
byteArrayOutputStream,
Int32(aesExtraDataRecord.getSignature().getOrThrow().getValue())
)
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(aesExtraDataRecord.getDataSize()))
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getAesVersion().getVersionNumber())
byteArrayOutputStream.write(HeaderUtil.getBytesFromString(aesExtraDataRecord.getVendorID()))
let aesStrengthBytes = Array<Byte>(1, repeat: 0)
aesStrengthBytes[0] = UInt8(aesExtraDataRecord.getAesKeyStrength().getRawCode())
byteArrayOutputStream.write(aesStrengthBytes)
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getCompressionMethod().getCode())
}
writeRemainingExtraDataRecordsIfPresent(fileHeader, byteArrayOutputStream)
if (fileCommentBytes.size > 0) {
byteArrayOutputStream.write(fileCommentBytes)
}
}
private func calculateExtraDataRecordsSize(fileHeader: FileHeader, writeZip64ExtendedInfo: Bool) {
var extraFieldLength: Int32 = 0
if (writeZip64ExtendedInfo) {
extraFieldLength += ZIP64_EXTRA_DATA_RECORD_SIZE_FH + 4 // 4 for signature + size of record
}
if (fileHeader.getAesExtraDataRecord().isSome()) {
extraFieldLength += AES_EXTRA_DATA_RECORD_SIZE
}
if (let Some(extraDataRecords) <- fileHeader.getExtraDataRecords()) {
for (i in 0..extraDataRecords.size) {
let extraDataRecord = extraDataRecords[i]
if (extraDataRecord.getHeader() == HeaderSignature.AES_EXTRA_DATA_RECORD.getValue() ||
extraDataRecord.getHeader() == HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue()) {
continue
}
extraFieldLength += 4 + Int32(extraDataRecord.getSizeOfData()) // 4 = 2 for header + 2 for size of data
}
}
return extraFieldLength
}
private func writeRemainingExtraDataRecordsIfPresent(fileHeader: FileHeader, outputStream: OutputStream) {
if (fileHeader.getExtraDataRecords().isNone() || fileHeader.getExtraDataRecords().getOrThrow().size == 0) {
return
}
let extraDataRecords = fileHeader.getExtraDataRecords().getOrThrow()
for (i in 0..extraDataRecords.size) {
let extraDataRecord = extraDataRecords[i]
if (extraDataRecord.getHeader() == HeaderSignature.AES_EXTRA_DATA_RECORD.getValue() ||
extraDataRecord.getHeader() == HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue()) {
continue
}
rawIO.writeShortLittleEndian(outputStream, Int32(extraDataRecord.getHeader()))
rawIO.writeShortLittleEndian(outputStream, Int32(extraDataRecord.getSizeOfData()))
if (extraDataRecord.getSizeOfData() > 0 && extraDataRecord.getData() != None) {
outputStream.write(extraDataRecord.getData().getOrThrow())
}
}
}
private func writeZip64EndOfCentralDirectoryRecord(
zip64EndOfCentralDirectoryRecord: Zip64EndOfCentralDirectoryRecord,
byteArrayOutputStream: ByteBuffer,
rawIO: RawIO
) {
rawIO.writeIntLittleEndian(
byteArrayOutputStream,
Int32(zip64EndOfCentralDirectoryRecord.getSignature().getOrThrow().getValue())
)
rawIO.writeLongLittleEndian(
byteArrayOutputStream,
zip64EndOfCentralDirectoryRecord.getSizeOfZip64EndCentralDirectoryRecord()
)
rawIO.writeShortLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getVersionMadeBy())
rawIO.writeShortLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getVersionNeededToExtract()
)
rawIO.writeIntLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getNumberOfThisDisk())
rawIO.writeIntLittleEndian(
byteArrayOutputStream,
zip64EndOfCentralDirectoryRecord.getNumberOfThisDiskStartOfCentralDirectory()
)
rawIO.writeLongLittleEndian(
byteArrayOutputStream,
zip64EndOfCentralDirectoryRecord.getTotalNumberOfEntriesInCentralDirectoryOnThisDisk()
)
rawIO.writeLongLittleEndian(
byteArrayOutputStream,
zip64EndOfCentralDirectoryRecord.getTotalNumberOfEntriesInCentralDirectory()
)
rawIO.writeLongLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getSizeOfCentralDirectory())
rawIO.writeLongLittleEndian(
byteArrayOutputStream,
zip64EndOfCentralDirectoryRecord.getOffsetStartCentralDirectoryWRTStartDiskNumber()
)
}
private func writeZip64EndOfCentralDirectoryLocator(
zip64EndOfCentralDirectoryLocator: Zip64EndOfCentralDirectoryLocator,
byteArrayOutputStream: ByteBuffer,
rawIO: RawIO
) {
rawIO.writeIntLittleEndian(
byteArrayOutputStream,
Int32(HeaderSignature.ZIP64_END_CENTRAL_DIRECTORY_LOCATOR.getValue())
)
rawIO.writeIntLittleEndian(
byteArrayOutputStream,
zip64EndOfCentralDirectoryLocator.getNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord()
)
rawIO.writeLongLittleEndian(
byteArrayOutputStream,
zip64EndOfCentralDirectoryLocator.getOffsetZip64EndOfCentralDirectoryRecord()
)
rawIO.writeIntLittleEndian(
byteArrayOutputStream,
zip64EndOfCentralDirectoryLocator.getTotalNumberOfDiscs()
)
}
private func writeEndOfCentralDirectoryRecord(
zipModel: ZipModel,
sizeOfCentralDir: Int32,
offsetCentralDir: Int64,
byteArrayOutputStream: ByteBuffer,
rawIO: RawIO
) {
let longByte = Array<Byte>(8, repeat: 0)
rawIO.writeIntLittleEndian(byteArrayOutputStream, Int32(HeaderSignature.END_OF_CENTRAL_DIRECTORY.getValue()))
rawIO.writeShortLittleEndian(
byteArrayOutputStream,
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk()
)
rawIO.writeShortLittleEndian(
byteArrayOutputStream,
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDiskStartOfCentralDir()
)
var numEntries = zipModel.getCentralDirectory().getOrThrow().getFileHeaders().size
var numEntriesOnThisDisk = numEntries
if (zipModel.isSplitArchive()) {
numEntriesOnThisDisk = countNumberOfFileHeaderEntriesOnDisk(
zipModel.getCentralDirectory().getOrThrow().getFileHeaders(),
Int64(zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk())
)
}
if (numEntriesOnThisDisk > InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT) {
numEntriesOnThisDisk = InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(numEntriesOnThisDisk))
if (numEntries > InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT) {
numEntries = InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(numEntries))
rawIO.writeIntLittleEndian(byteArrayOutputStream, sizeOfCentralDir)
if (offsetCentralDir > InternalZipConstants.ZIP_64_SIZE_LIMIT) {
rawIO.writeLongLittleEndian(longByte, 0, InternalZipConstants.ZIP_64_SIZE_LIMIT)
byteArrayOutputStream.write(longByte[0..4])
} else {
rawIO.writeLongLittleEndian(longByte, 0, offsetCentralDir)
byteArrayOutputStream.write(longByte[0..4])
}
var comment = zipModel.getEndOfCentralDirectoryRecord().getComment()
if (Zip4cjUtil.isStringNotNullAndNotEmpty(comment)) {
let commentBytes = HeaderUtil.getBytesFromString(comment)
rawIO.writeShortLittleEndian(byteArrayOutputStream, Int32(commentBytes.size))
byteArrayOutputStream.write(commentBytes)
} else {
rawIO.writeShortLittleEndian(byteArrayOutputStream, 0)
}
}
private func countNumberOfFileHeaderEntriesOnDisk(fileHeaders: ArrayList<FileHeader>, numOfDisk: Int64): Int64 {
var noEntries = 0
for (i in 0..fileHeaders.size) {
if (fileHeaders[i].getDiskNumberStart() == numOfDisk) {
noEntries++
}
}
return noEntries
}
private func isZip64Entry(fileHeader: FileHeader): Bool {
return fileHeader.getCompressedSize() >= InternalZipConstants.ZIP_64_SIZE_LIMIT ||
fileHeader.getUncompressedSize() >= InternalZipConstants.ZIP_64_SIZE_LIMIT ||
fileHeader.getOffsetLocalHeader() >= InternalZipConstants.ZIP_64_SIZE_LIMIT ||
fileHeader.getDiskNumberStart() >= InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT
}
private func getOffsetOfCentralDirectory(zipModel: ZipModel): Int64 {
if (zipModel.isZip64Format() &&
zipModel.getZip64EndOfCentralDirectoryRecord().getOffsetStartCentralDirectoryWRTStartDiskNumber() != -1) {
return zipModel.getZip64EndOfCentralDirectoryRecord().getOffsetStartCentralDirectoryWRTStartDiskNumber()
}
return zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory()
}
private func buildZip64EndOfCentralDirectoryRecord(
zipModel: ZipModel,
sizeOfCentralDir: Int64,
offsetCentralDir: Int64
): Zip64EndOfCentralDirectoryRecord {
let zip64EndOfCentralDirectoryRecord = Zip64EndOfCentralDirectoryRecord()
zip64EndOfCentralDirectoryRecord.setSignature(HeaderSignature.ZIP64_END_CENTRAL_DIRECTORY_RECORD)
zip64EndOfCentralDirectoryRecord.setSizeOfZip64EndCentralDirectoryRecord(44)
if (zipModel.getCentralDirectory().getOrThrow().getFileHeaders().size > 0) {
var firstFileHeader = zipModel.getCentralDirectory().getOrThrow().getFileHeaders()[0]
zip64EndOfCentralDirectoryRecord.setVersionMadeBy(firstFileHeader.getVersionMadeBy())
zip64EndOfCentralDirectoryRecord.setVersionNeededToExtract(firstFileHeader.getVersionNeededToExtract())
}
zip64EndOfCentralDirectoryRecord.setNumberOfThisDisk(
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk())
zip64EndOfCentralDirectoryRecord.setNumberOfThisDiskStartOfCentralDirectory(
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDiskStartOfCentralDir())
var numEntries = zipModel.getCentralDirectory().getOrThrow().getFileHeaders().size
var numEntriesOnThisDisk = numEntries
if (zipModel.isSplitArchive()) {
numEntriesOnThisDisk = countNumberOfFileHeaderEntriesOnDisk(
zipModel.getCentralDirectory().getOrThrow().getFileHeaders(),
Int64(zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk())
)
}
zip64EndOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(numEntriesOnThisDisk)
zip64EndOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectory(numEntries)
zip64EndOfCentralDirectoryRecord.setSizeOfCentralDirectory(Int64(sizeOfCentralDir))
zip64EndOfCentralDirectoryRecord.setOffsetStartCentralDirectoryWRTStartDiskNumber(offsetCentralDir)
return zip64EndOfCentralDirectoryRecord
}
}