/*
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved.
*/
package zip4cj.headers
public class FileHeaderFactory {
public func generateFileHeader(
zipParameters: ZipParameters,
isSplitZip: Bool,
currentDiskNumberStart: Int64,
rawIO: RawIO
): FileHeader {
let fh = FileHeader()
fh.setSignature(HeaderSignature.CENTRAL_DIRECTORY)
fh.setVersionMadeBy(ZipVersionUtils.determineVersionMadeBy(zipParameters, rawIO))
fh.setVersionNeededToExtract(ZipVersionUtils.determineVersionNeededToExtract(zipParameters).getCode())
if (zipParameters.isEncryptFiles() && zipParameters.getEncryptionMethod() == EncryptionMethod.AES) {
fh.setCompressionMethod(CompressionMethod.AES_INTERNAL_ONLY)
fh.setAesExtraDataRecord(generateAESExtraDataRecord(zipParameters))
fh.setExtraFieldLength(
fh.getExtraFieldLength() + InternalZipConstants.AES_EXTRA_DATA_RECORD_SIZE)
} else {
fh.setCompressionMethod(zipParameters.getCompressionMethod())
}
if (zipParameters.isEncryptFiles()) {
if (zipParameters.getEncryptionMethod() == EncryptionMethod.NONE) {
throw ZipException("Encryption method has to be set when encryptFiles flag is set in zip parameters")
}
fh.setEncrypted(true)
fh.setEncryptionMethod(zipParameters.getEncryptionMethod())
}
var fileName = validateAndGetFileName(zipParameters.getFileNameInZip())
fh.setFileName(fileName)
fh.setFileNameLength(determineFileNameLength(fileName))
fh.setDiskNumberStart(if (isSplitZip) {
currentDiskNumberStart
} else {
0
})
fh.setLastModifiedTime(Zip4cjUtil.epochToExtendedDosTime(zipParameters.getLastModifiedFileTime()))
var isDirectory = FileUtils.isZipEntryDirectory(fileName)
fh.setDirectory(isDirectory)
fh.setExternalFileAttributes(FileUtils.getDefaultFileAttributes(isDirectory))
if (zipParameters.isWriteExtendedLocalFileHeader() && zipParameters.getEntrySize() == -1) {
fh.setUncompressedSize(0)
} else {
fh.setUncompressedSize(zipParameters.getEntrySize())
}
if (zipParameters.isEncryptFiles() && zipParameters.getEncryptionMethod() == EncryptionMethod.ZIP_STANDARD) {
fh.setCrc(zipParameters.getEntryCRC())
}
fh.setGeneralPurposeFlag(determineGeneralPurposeBitFlag(fh.isEncrypted(), zipParameters))
fh.setDataDescriptorExists(zipParameters.isWriteExtendedLocalFileHeader())
fh.setFileComment(zipParameters.getFileComment())
return fh
}
public func generateLocalFileHeader(fh: FileHeader): LocalFileHeader {
let localFileHeader = LocalFileHeader()
localFileHeader.setSignature(HeaderSignature.LOCAL_FILE_HEADER)
localFileHeader.setVersionNeededToExtract(fh.getVersionNeededToExtract())
localFileHeader.setCompressionMethod(fh.getCompressionMethod())
localFileHeader.setLastModifiedTime(fh.getLastModifiedTime())
localFileHeader.setUncompressedSize(fh.getUncompressedSize())
localFileHeader.setFileNameLength(fh.getFileNameLength())
localFileHeader.setFileName(fh.getFileName())
localFileHeader.setEncrypted(fh.isEncrypted())
localFileHeader.setEncryptionMethod(fh.getEncryptionMethod())
localFileHeader.setAesExtraDataRecord(fh.getAesExtraDataRecord())
localFileHeader.setCrc(fh.getCrc())
localFileHeader.setCompressedSize(fh.getCompressedSize())
localFileHeader.setGeneralPurposeFlag(fh.getGeneralPurposeFlag().getOrThrow({=> throw NoneValueException("FileHeader.getGeneralPurposeFlag() is null")}).clone())
localFileHeader.setDataDescriptorExists(fh.isDataDescriptorExists())
localFileHeader.setExtraFieldLength(fh.getExtraFieldLength())
return localFileHeader
}
private func determineGeneralPurposeBitFlag(isEncrypted: Bool, zipParameters: ZipParameters): Array<Byte> {
var generalPurposeBitFlag = Array<Byte>(2, repeat: 0)
generalPurposeBitFlag[0] = generateFirstGeneralPurposeByte(isEncrypted, zipParameters)
generalPurposeBitFlag[1] = BitUtils.setBit(generalPurposeBitFlag[1], 3)
return generalPurposeBitFlag
}
private func generateFirstGeneralPurposeByte(isEncrypted: Bool, zipParameters: ZipParameters): Byte {
var firstByte: Byte = 0
if (isEncrypted) {
firstByte = BitUtils.setBit(firstByte, 0)
}
if (CompressionMethod.DEFLATE == (zipParameters.getCompressionMethod())) {
if (CompressionLevel.NORMAL == (zipParameters.getCompressionLevel())) {
firstByte = BitUtils.unsetBit(firstByte, 1)
firstByte = BitUtils.unsetBit(firstByte, 2)
} else if (CompressionLevel.MAXIMUM == (zipParameters.getCompressionLevel())) {
firstByte = BitUtils.setBit(firstByte, 1)
firstByte = BitUtils.unsetBit(firstByte, 2)
} else if (CompressionLevel.FAST == (zipParameters.getCompressionLevel())) {
firstByte = BitUtils.unsetBit(firstByte, 1)
firstByte = BitUtils.setBit(firstByte, 2)
} else if (CompressionLevel.FASTEST == (zipParameters.getCompressionLevel()) || CompressionLevel.ULTRA == (zipParameters.
getCompressionLevel())) {
firstByte = BitUtils.setBit(firstByte, 1)
firstByte = BitUtils.setBit(firstByte, 2)
}
}
if (zipParameters.isWriteExtendedLocalFileHeader()) {
firstByte = BitUtils.setBit(firstByte, 3)
}
return firstByte
}
private func validateAndGetFileName(fileNameInZip: ?String): String {
if (!Zip4cjUtil.isStringNotNullAndNotEmpty(fileNameInZip)) {
throw ZipException("fileNameInZip is null or empty")
}
return fileNameInZip.getOrThrow()
}
private func generateAESExtraDataRecord(parameters: ZipParameters): AESExtraDataRecord {
var aesData = AESExtraDataRecord()
aesData.setAesVersion(parameters.getAesVersion())
if (parameters.getAesKeyStrength() == AesKeyStrength.KEY_STRENGTH_128) {
aesData.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_128)
} else if (parameters.getAesKeyStrength() == AesKeyStrength.KEY_STRENGTH_192) {
aesData.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_192)
} else if (parameters.getAesKeyStrength() == AesKeyStrength.KEY_STRENGTH_256) {
aesData.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256)
} else {
throw ZipException("invalid AES key strength")
}
aesData.setCompressionMethod(parameters.getCompressionMethod())
return aesData
}
private func determineFileNameLength(fileName: String): Int64 {
return HeaderUtil.getBytesFromString(fileName).size
}
}