From d6d29f32d4febbde7da226ff4a8d0446841b4b27 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 10 Oct 2017 16:36:56 +0300 Subject: [PATCH] Restore conformance of Zip classes to Container... protocols Also remove unnecessary code from ZipEntry --- Sources/ZIP/ZipContainer.swift | 99 ++++++++++++- Sources/ZIP/ZipEntry.swift | 255 +-------------------------------- Sources/ZIP/ZipEntryInfo.swift | 2 +- 3 files changed, 105 insertions(+), 251 deletions(-) diff --git a/Sources/ZIP/ZipContainer.swift b/Sources/ZIP/ZipContainer.swift index a0f18ce3..3de85142 100644 --- a/Sources/ZIP/ZipContainer.swift +++ b/Sources/ZIP/ZipContainer.swift @@ -6,7 +6,7 @@ import Foundation /// Provides open function for ZIP containers. -public class ZipContainer { +public class ZipContainer: Container { /** Processes ZIP container and returns an array of `ContainerEntry` (which are actually `ZipEntry`). @@ -50,10 +50,105 @@ public class ZipContainer { for _ in 0.. Data { + // Now, let's move to the location of local header. + pointerData.index = Int(UInt32(truncatingIfNeeded: info.cdEntry.offset)) + + let localHeader = try ZipLocalHeader(pointerData) + // Check local header for consistency with Central Directory entry. + guard localHeader.generalPurposeBitFlags == info.cdEntry.generalPurposeBitFlags && + localHeader.compressionMethod == info.cdEntry.compressionMethod && + localHeader.lastModFileTime == info.cdEntry.lastModFileTime && + localHeader.lastModFileDate == info.cdEntry.lastModFileDate + else { throw ZipError.wrongLocalHeader } + + let hasDataDescriptor = localHeader.generalPurposeBitFlags & 0x08 != 0 + + // If file has data descriptor, then some values in local header are absent. + // So we need to use values from CD entry. + var uncompSize = hasDataDescriptor ? + Int(UInt32(truncatingIfNeeded: info.cdEntry.uncompSize)) : + Int(UInt32(truncatingIfNeeded: localHeader.uncompSize)) + var compSize = hasDataDescriptor ? + Int(UInt32(truncatingIfNeeded: info.cdEntry.compSize)) : + Int(UInt32(truncatingIfNeeded: localHeader.compSize)) + var crc32 = hasDataDescriptor ? info.cdEntry.crc32 : localHeader.crc32 + + let fileBytes: [UInt8] + let fileDataStart = pointerData.index + switch localHeader.compressionMethod { + case 0: + fileBytes = pointerData.bytes(count: uncompSize) + case 8: + let bitReader = BitReader(data: pointerData.data, bitOrder: .reversed) + bitReader.index = pointerData.index + fileBytes = try Deflate.decompress(bitReader) + // Sometimes pointerData stays in not-aligned state after deflate decompression. + // Following line ensures that this is not the case. + bitReader.align() + pointerData.index = bitReader.index + case 12: + #if (!SWCOMPRESSION_POD_ZIP) || (SWCOMPRESSION_POD_ZIP && SWCOMPRESSION_POD_BZ2) + // BZip2 algorithm considers bits in a byte in a different order. + let bitReader = BitReader(data: pointerData.data, bitOrder: .straight) + bitReader.index = pointerData.index + fileBytes = try BZip2.decompress(bitReader) + bitReader.align() + pointerData.index = bitReader.index + #else + throw ZipError.compressionNotSupported + #endif + case 14: + #if (!SWCOMPRESSION_POD_ZIP) || (SWCOMPRESSION_POD_ZIP && SWCOMPRESSION_POD_LZMA) + pointerData.index += 4 // Skipping LZMA SDK version and size of properties. + let lzmaDecoder = try LZMADecoder(pointerData) + try lzmaDecoder.decodeLZMA(uncompSize) + fileBytes = lzmaDecoder.out + #else + throw ZipError.compressionNotSupported + #endif + default: + throw ZipError.compressionNotSupported + } + let realCompSize = pointerData.index - fileDataStart + + if hasDataDescriptor { + // Now we need to parse data descriptor itself. + // First, it might or might not have signature. + let ddSignature = pointerData.uint32() + if ddSignature != 0x08074b50 { + pointerData.index -= 4 + } + // Now, let's update from CD with values from data descriptor. + crc32 = pointerData.uint32() + let sizeOfSizeField: UInt64 = localHeader.zip64FieldsArePresent ? 8 : 4 + compSize = Int(pointerData.uint64(count: sizeOfSizeField)) + uncompSize = Int(pointerData.uint64(count: sizeOfSizeField)) + } + + guard compSize == realCompSize && uncompSize == fileBytes.count + else { throw ZipError.wrongSize } + guard crc32 == UInt32(CheckSums.crc32(fileBytes)) + else { throw ZipError.wrongCRC32(Data(bytes: fileBytes)) } + + return Data(bytes: fileBytes) + } + + public static func info(container: Data) throws -> [ZipEntryInfo] { + return [] + } + } diff --git a/Sources/ZIP/ZipEntry.swift b/Sources/ZIP/ZipEntry.swift index 9506f8c8..ffecd51c 100644 --- a/Sources/ZIP/ZipEntry.swift +++ b/Sources/ZIP/ZipEntry.swift @@ -6,257 +6,16 @@ import Foundation /// Represents either a file or directory entry in ZIP container. -public class ZipEntry { +public class ZipEntry: ContainerEntry { - private let cdEntry: ZipCentralDirectoryEntry - private var localHeader: ZipLocalHeader? - private let containerData: Data + /// Various information about entry. + public let info: ZipEntryInfo - /// Name of the file or directory. - public var name: String { - return self.cdEntry.fileName - } + public let data: Data? - /// Comment associated with the entry. - public var comment: String? { - return self.cdEntry.fileComment - } - - /// File or directory attributes related to the file system of the container's creator. - public var attributes: UInt32 { - return self.cdEntry.externalFileAttributes - } - - /// Size of the data associated with the entry. - public var size: Int { - return Int(truncatingIfNeeded: cdEntry.uncompSize) - } - - /** - True, if entry is a directory. - For MS-DOS and UNIX-like container creator's OS, the result is based on 'external file attributes'. - Otherwise, it is true if size of data is 0 AND last character of entry's name is '/'. - */ - public var isDirectory: Bool { - if let fileType = entryAttributes[FileAttributeKey.type] as? FileAttributeType { - return fileType == FileAttributeType.typeDirectory - } else { - return size == 0 && name.last == "/" - } - } - - /// True, if entry is a symbolic link. - public let isLink: Bool - - /// Path to a linked file for symbolic link entry. - public lazy var linkPath: String? = { - guard self.isLink, let entryData = try? self.data() - else { return nil } - return String(data: entryData, encoding: .utf8) - }() - - /// True if entry is likely to be text or ASCII file. - public var isTextFile: Bool { - return cdEntry.internalFileAttributes & 0x1 != 0 - } - - /** - Provides a dictionary with various attributes of the entry. - `FileAttributeKey` values are used as dictionary keys. - - ## Possible attributes: - - - `FileAttributeKey.modificationDate` - - `FileAttributeKey.size` - - `FileAttributeKey.type`, only if origin OS was UNIX- or DOS-like. - - `FileAttributeKey.posixPermissions`, only if origin OS was UNIX-like. - - `FileAttributeKey.appendOnly`, only if origin OS was DOS-like. - */ - public let entryAttributes: [FileAttributeKey: Any] - - /** - Returns data associated with this entry. - - - Throws: `ZipError` or any other error associated with compression type, - depending on the type of the problem. An error can indicate that container is damaged. - */ - public func data() throws -> Data { - // Now, let's move to the location of local header. - let pointerData = DataWithPointer(data: self.containerData) - pointerData.index = Int(UInt32(truncatingIfNeeded: self.cdEntry.offset)) - - if localHeader == nil { - localHeader = try ZipLocalHeader(pointerData) - // Check local header for consistency with Central Directory entry. - guard localHeader!.generalPurposeBitFlags == cdEntry.generalPurposeBitFlags && - localHeader!.compressionMethod == cdEntry.compressionMethod && - localHeader!.lastModFileTime == cdEntry.lastModFileTime && - localHeader!.lastModFileDate == cdEntry.lastModFileDate - else { throw ZipError.wrongLocalHeader } - } else { - pointerData.index += localHeader!.headerSize - } - - let hasDataDescriptor = localHeader!.generalPurposeBitFlags & 0x08 != 0 - - // If file has data descriptor, then some values in local header are absent. - // So we need to use values from CD entry. - var uncompSize = hasDataDescriptor ? - Int(UInt32(truncatingIfNeeded: cdEntry.uncompSize)) : - Int(UInt32(truncatingIfNeeded: localHeader!.uncompSize)) - var compSize = hasDataDescriptor ? - Int(UInt32(truncatingIfNeeded: cdEntry.compSize)) : - Int(UInt32(truncatingIfNeeded: localHeader!.compSize)) - var crc32 = hasDataDescriptor ? cdEntry.crc32 : localHeader!.crc32 - - let fileBytes: [UInt8] - let fileDataStart = pointerData.index - switch localHeader!.compressionMethod { - case 0: - fileBytes = pointerData.bytes(count: uncompSize) - case 8: - let bitReader = BitReader(data: pointerData.data, bitOrder: .reversed) - bitReader.index = pointerData.index - fileBytes = try Deflate.decompress(bitReader) - // Sometimes pointerData stays in not-aligned state after deflate decompression. - // Following line ensures that this is not the case. - bitReader.align() - pointerData.index = bitReader.index - case 12: - #if (!SWCOMPRESSION_POD_ZIP) || (SWCOMPRESSION_POD_ZIP && SWCOMPRESSION_POD_BZ2) - // BZip2 algorithm considers bits in a byte in a different order. - let bitReader = BitReader(data: pointerData.data, bitOrder: .straight) - bitReader.index = pointerData.index - fileBytes = try BZip2.decompress(bitReader) - bitReader.align() - pointerData.index = bitReader.index - #else - throw ZipError.compressionNotSupported - #endif - case 14: - #if (!SWCOMPRESSION_POD_ZIP) || (SWCOMPRESSION_POD_ZIP && SWCOMPRESSION_POD_LZMA) - pointerData.index += 4 // Skipping LZMA SDK version and size of properties. - let lzmaDecoder = try LZMADecoder(pointerData) - try lzmaDecoder.decodeLZMA(uncompSize) - fileBytes = lzmaDecoder.out - #else - throw ZipError.compressionNotSupported - #endif - default: - throw ZipError.compressionNotSupported - } - let realCompSize = pointerData.index - fileDataStart - - if hasDataDescriptor { - // Now we need to parse data descriptor itself. - // First, it might or might not have signature. - let ddSignature = pointerData.uint32() - if ddSignature != 0x08074b50 { - pointerData.index -= 4 - } - // Now, let's update from CD with values from data descriptor. - crc32 = pointerData.uint32() - let sizeOfSizeField: UInt64 = localHeader!.zip64FieldsArePresent ? 8 : 4 - compSize = Int(pointerData.uint64(count: sizeOfSizeField)) - uncompSize = Int(pointerData.uint64(count: sizeOfSizeField)) - } - - guard compSize == realCompSize && uncompSize == fileBytes.count - else { throw ZipError.wrongSize } - guard crc32 == UInt32(CheckSums.crc32(fileBytes)) - else { throw ZipError.wrongCRC32(Data(bytes: fileBytes)) } - - return Data(bytes: fileBytes) - } - - init(_ cdEntry: ZipCentralDirectoryEntry, _ pointerData: DataWithPointer) { - self.cdEntry = cdEntry - self.containerData = pointerData.data - - var attributesDict = [FileAttributeKey: Any]() - - // Modification time - let dosDate = cdEntry.lastModFileDate.toInt() - - let day = dosDate & 0x1F - let month = (dosDate & 0x1E0) >> 5 - let year = 1980 + ((dosDate & 0xFE00) >> 9) - - let dosTime = cdEntry.lastModFileTime.toInt() - - let seconds = 2 * (dosTime & 0x1F) - let minutes = (dosTime & 0x7E0) >> 5 - let hours = (dosTime & 0xF800) >> 11 - - if let mtime = DateComponents(calendar: Calendar(identifier: .iso8601), - timeZone: TimeZone(abbreviation: "UTC"), - year: year, month: month, day: day, - hour: hours, minute: minutes, second: seconds).date { - attributesDict[FileAttributeKey.modificationDate] = mtime - } - - // Extended Timestamp - if let mtimestamp = cdEntry.modificationTimestamp { - attributesDict[FileAttributeKey.modificationDate] = Date(timeIntervalSince1970: TimeInterval(mtimestamp)) - } - - // NTFS Extra Fields - if let mtime = Date(from: cdEntry.ntfsMtime) { - attributesDict[FileAttributeKey.modificationDate] = mtime - } - if let ctime = Date(from: cdEntry.ntfsCtime) { - attributesDict[FileAttributeKey.creationDate] = ctime - } - - // Size - attributesDict[FileAttributeKey.size] = cdEntry.uncompSize - - // External file attributes. - - // For unix-like origin systems we can parse extended attributes. - let hostSystem = (cdEntry.versionMadeBy & 0xFF00) >> 8 - if hostSystem == 3 { - // File type. - let fileType = (cdEntry.externalFileAttributes & 0xF0000000) >> 28 - switch fileType { - case 0x2: - attributesDict[FileAttributeKey.type] = FileAttributeType.typeCharacterSpecial - case 0x4: - attributesDict[FileAttributeKey.type] = FileAttributeType.typeDirectory - case 0x6: - attributesDict[FileAttributeKey.type] = FileAttributeType.typeBlockSpecial - case 0x8: - attributesDict[FileAttributeKey.type] = FileAttributeType.typeRegular - case 0xA: - attributesDict[FileAttributeKey.type] = FileAttributeType.typeSymbolicLink - case 0xC: - attributesDict[FileAttributeKey.type] = FileAttributeType.typeSocket - default: - attributesDict[FileAttributeKey.type] = FileAttributeType.typeUnknown - } - - // Posix permissions. - let posixPermissions = (cdEntry.externalFileAttributes & 0x0FFF0000) >> 16 - attributesDict[FileAttributeKey.posixPermissions] = posixPermissions - } - - // For dos and unix-like systems we can parse dos attributes. - if hostSystem == 0 || hostSystem == 3 { - let dosAttributes = cdEntry.externalFileAttributes & 0xFF - - if dosAttributes & 0x10 != 0 && hostSystem == 0 { - attributesDict[FileAttributeKey.type] = FileAttributeType.typeDirectory - } else if hostSystem == 0 { - attributesDict[FileAttributeKey.type] = FileAttributeType.typeRegular - } - - if dosAttributes & 0x1 != 0 { - attributesDict[FileAttributeKey.appendOnly] = true - } - } - - self.entryAttributes = attributesDict - self.isLink = attributesDict[FileAttributeKey.type] as? FileAttributeType == FileAttributeType.typeSymbolicLink + init(_ entryInfo: ZipEntryInfo, _ data: Data?) { + self.info = entryInfo + self.data = data } } diff --git a/Sources/ZIP/ZipEntryInfo.swift b/Sources/ZIP/ZipEntryInfo.swift index b3720373..ccc6ea11 100644 --- a/Sources/ZIP/ZipEntryInfo.swift +++ b/Sources/ZIP/ZipEntryInfo.swift @@ -7,7 +7,7 @@ import Foundation public class ZipEntryInfo: ContainerEntryInfo { - private let cdEntry: ZipCentralDirectoryEntry + let cdEntry: ZipCentralDirectoryEntry private var localHeader: ZipLocalHeader? private let containerData: Data