ReadViewSDK/ReadViewDemo/Pods/ZIPFoundation/Sources/ZIPFoundation/Entry.swift
2026-05-21 19:40:51 +08:00

383 lines
17 KiB
Swift

//
// Entry.swift
// ZIPFoundation
//
// Copyright © 2017-2025 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors.
// Released under the MIT License.
//
// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
//
import Foundation
import CoreFoundation
/// A value that represents a file, a directory or a symbolic link within a ZIP `Archive`.
///
/// You can retrieve instances of `Entry` from an `Archive` via subscripting or iteration.
/// Entries are identified by their `path`.
public struct Entry: Equatable {
/// The type of an `Entry` in a ZIP `Archive`.
public enum EntryType: Int {
/// Indicates a regular file.
case file
/// Indicates a directory.
case directory
/// Indicates a symbolic link.
case symlink
init(mode: mode_t) {
switch mode & S_IFMT {
case S_IFDIR:
self = .directory
case S_IFLNK:
self = .symlink
default:
self = .file
}
}
}
enum OSType: UInt {
case msdos = 0
case unix = 3
case osx = 19
case unused = 20
}
struct LocalFileHeader: DataSerializable {
let localFileHeaderSignature = UInt32(localFileHeaderStructSignature)
let versionNeededToExtract: UInt16
let generalPurposeBitFlag: UInt16
let compressionMethod: UInt16
let lastModFileTime: UInt16
let lastModFileDate: UInt16
let crc32: UInt32
let compressedSize: UInt32
let uncompressedSize: UInt32
let fileNameLength: UInt16
let extraFieldLength: UInt16
static let size = 30
let fileNameData: Data
let extraFieldData: Data
var extraFields: [ExtensibleDataField]?
}
struct DataDescriptor<T: BinaryInteger>: DataSerializable {
let data: Data
let dataDescriptorSignature = UInt32(dataDescriptorStructSignature)
let crc32: UInt32
// For normal archives, the compressed and uncompressed sizes are 4 bytes each.
// For ZIP64 format archives, the compressed and uncompressed sizes are 8 bytes each.
let compressedSize: T
let uncompressedSize: T
static var memoryLengthOfSize: Int { MemoryLayout<T>.size }
static var size: Int { memoryLengthOfSize * 2 + 8 }
}
typealias DefaultDataDescriptor = DataDescriptor<UInt32>
typealias ZIP64DataDescriptor = DataDescriptor<UInt64>
struct CentralDirectoryStructure: DataSerializable {
let centralDirectorySignature = UInt32(centralDirectoryStructSignature)
let versionMadeBy: UInt16
let versionNeededToExtract: UInt16
let generalPurposeBitFlag: UInt16
let compressionMethod: UInt16
let lastModFileTime: UInt16
let lastModFileDate: UInt16
let crc32: UInt32
let compressedSize: UInt32
let uncompressedSize: UInt32
let fileNameLength: UInt16
let extraFieldLength: UInt16
let fileCommentLength: UInt16
let diskNumberStart: UInt16
let internalFileAttributes: UInt16
let externalFileAttributes: UInt32
let relativeOffsetOfLocalHeader: UInt32
static let size = 46
let fileNameData: Data
let extraFieldData: Data
let fileCommentData: Data
var extraFields: [ExtensibleDataField]?
var usesDataDescriptor: Bool { return (self.generalPurposeBitFlag & (1 << 3 )) != 0 }
var usesUTF8PathEncoding: Bool { return (self.generalPurposeBitFlag & (1 << 11 )) != 0 }
var isEncrypted: Bool { return (self.generalPurposeBitFlag & (1 << 0)) != 0 }
var isZIP64: Bool {
// If ZIP64 extended information is existing, try to treat cd as ZIP64 format
// even if the version needed to extract is lower than 4.5
return UInt8(truncatingIfNeeded: self.versionNeededToExtract) >= 45 || zip64ExtendedInformation != nil
}
}
/// Returns the `path` of the receiver within a ZIP `Archive` using a given encoding.
///
/// - Parameters:
/// - encoding: `String.Encoding`
public func path(using encoding: String.Encoding) -> String {
return String(pathData: self.centralDirectoryStructure.fileNameData, encoding: encoding)
}
/// The `path` of the receiver within a ZIP `Archive`.
public var path: String {
let encoding = self.centralDirectoryStructure.usesUTF8PathEncoding ? String.Encoding.utf8 : .codepage437
return self.path(using: encoding)
}
/// The file attributes of the receiver as key/value pairs.
///
/// Contains the modification date and file permissions.
public var fileAttributes: [FileAttributeKey: Any] {
return FileManager.attributes(from: self)
}
/// The `CRC32` checksum of the receiver.
///
/// - Note: Always returns `0` for entries of type `EntryType.directory`.
public var checksum: CRC32 {
if self.centralDirectoryStructure.usesDataDescriptor {
return self.zip64DataDescriptor?.crc32 ?? self.dataDescriptor?.crc32 ?? 0
}
return self.centralDirectoryStructure.crc32
}
/// The `EntryType` of the receiver.
public var type: EntryType {
// OS Type is stored in the upper byte of versionMadeBy
let osTypeRaw = self.centralDirectoryStructure.versionMadeBy >> 8
let osType = OSType(rawValue: UInt(osTypeRaw)) ?? .unused
var isDirectory = self.path.hasSuffix("/")
switch osType {
case .unix, .osx:
// Use truncatingIfNeeded for safer conversion across platforms
let modeValue = UInt16(truncatingIfNeeded: self.centralDirectoryStructure.externalFileAttributes >> 16)
let mode = mode_t(modeValue) & S_IFMT
switch mode {
case S_IFREG:
return .file
case S_IFDIR:
return .directory
case S_IFLNK:
return .symlink
default:
return isDirectory ? .directory : .file
}
case .msdos:
isDirectory = isDirectory || ((centralDirectoryStructure.externalFileAttributes >> 4) == 0x01)
fallthrough // For all other OSes we can only guess based on the directory suffix char
default: return isDirectory ? .directory : .file
}
}
/// Indicates whether or not the receiver is compressed.
public var isCompressed: Bool {
self.localFileHeader.compressionMethod != CompressionMethod.none.rawValue
}
/// The size of the receiver's compressed data.
public var compressedSize: UInt64 {
if centralDirectoryStructure.isZIP64 {
return zip64DataDescriptor?.compressedSize ?? centralDirectoryStructure.effectiveCompressedSize
}
return UInt64(dataDescriptor?.compressedSize ?? centralDirectoryStructure.compressedSize)
}
/// The size of the receiver's uncompressed data.
public var uncompressedSize: UInt64 {
if centralDirectoryStructure.isZIP64 {
return zip64DataDescriptor?.uncompressedSize ?? centralDirectoryStructure.effectiveUncompressedSize
}
return UInt64(dataDescriptor?.uncompressedSize ?? centralDirectoryStructure.uncompressedSize)
}
/// The combined size of the local header, the data and the optional data descriptor.
var localSize: UInt64 {
let localFileHeader = self.localFileHeader
var extraDataLength = Int(localFileHeader.fileNameLength)
extraDataLength += Int(localFileHeader.extraFieldLength)
var size = UInt64(LocalFileHeader.size + extraDataLength)
size += self.isCompressed ? self.compressedSize : self.uncompressedSize
if centralDirectoryStructure.isZIP64 {
size += self.zip64DataDescriptor != nil ? UInt64(ZIP64DataDescriptor.size) : 0
} else {
size += self.dataDescriptor != nil ? UInt64(DefaultDataDescriptor.size) : 0
}
return size
}
var dataOffset: UInt64 {
var dataOffset = self.centralDirectoryStructure.effectiveRelativeOffsetOfLocalHeader
dataOffset += UInt64(LocalFileHeader.size)
dataOffset += UInt64(self.localFileHeader.fileNameLength)
dataOffset += UInt64(self.localFileHeader.extraFieldLength)
return dataOffset
}
let centralDirectoryStructure: CentralDirectoryStructure
let localFileHeader: LocalFileHeader
let dataDescriptor: DefaultDataDescriptor?
let zip64DataDescriptor: ZIP64DataDescriptor?
public static func == (lhs: Entry, rhs: Entry) -> Bool {
return lhs.path == rhs.path
&& lhs.localFileHeader.crc32 == rhs.localFileHeader.crc32
&& lhs.centralDirectoryStructure.effectiveRelativeOffsetOfLocalHeader
== rhs.centralDirectoryStructure.effectiveRelativeOffsetOfLocalHeader
}
init?(centralDirectoryStructure: CentralDirectoryStructure,
localFileHeader: LocalFileHeader,
dataDescriptor: DefaultDataDescriptor? = nil,
zip64DataDescriptor: ZIP64DataDescriptor? = nil) {
// We currently don't support encrypted archives
guard !centralDirectoryStructure.isEncrypted else { return nil }
self.centralDirectoryStructure = centralDirectoryStructure
self.localFileHeader = localFileHeader
self.dataDescriptor = dataDescriptor
self.zip64DataDescriptor = zip64DataDescriptor
}
}
extension Entry.CentralDirectoryStructure {
init(localFileHeader: Entry.LocalFileHeader, fileAttributes: UInt32, relativeOffset: UInt32,
extraField: (length: UInt16, data: Data)) {
self.versionMadeBy = UInt16(789)
self.versionNeededToExtract = localFileHeader.versionNeededToExtract
self.generalPurposeBitFlag = localFileHeader.generalPurposeBitFlag
self.compressionMethod = localFileHeader.compressionMethod
self.lastModFileTime = localFileHeader.lastModFileTime
self.lastModFileDate = localFileHeader.lastModFileDate
self.crc32 = localFileHeader.crc32
self.compressedSize = localFileHeader.compressedSize
self.uncompressedSize = localFileHeader.uncompressedSize
self.fileNameLength = localFileHeader.fileNameLength
self.extraFieldLength = extraField.length
self.fileCommentLength = UInt16(0)
self.diskNumberStart = UInt16(0)
self.internalFileAttributes = UInt16(0)
self.externalFileAttributes = fileAttributes
self.relativeOffsetOfLocalHeader = relativeOffset
self.fileNameData = localFileHeader.fileNameData
self.extraFieldData = extraField.data
self.fileCommentData = Data()
if let zip64ExtendedInformation = Entry.ZIP64ExtendedInformation.scanForZIP64Field(in: self.extraFieldData,
fields: self.validFields) {
self.extraFields = [zip64ExtendedInformation]
}
}
init(centralDirectoryStructure: Entry.CentralDirectoryStructure,
zip64ExtendedInformation: Entry.ZIP64ExtendedInformation?, relativeOffset: UInt32) {
if let existingInfo = zip64ExtendedInformation {
self.extraFieldData = existingInfo.data
self.versionNeededToExtract = max(centralDirectoryStructure.versionNeededToExtract,
Archive.Version.v45.rawValue)
} else {
self.extraFieldData = centralDirectoryStructure.extraFieldData
let existingVersion = centralDirectoryStructure.versionNeededToExtract
self.versionNeededToExtract = existingVersion < Archive.Version.v45.rawValue
? centralDirectoryStructure.versionNeededToExtract
: Archive.Version.v20.rawValue
}
self.extraFieldLength = UInt16(extraFieldData.count)
self.relativeOffsetOfLocalHeader = relativeOffset
self.versionMadeBy = centralDirectoryStructure.versionMadeBy
self.generalPurposeBitFlag = centralDirectoryStructure.generalPurposeBitFlag
self.compressionMethod = centralDirectoryStructure.compressionMethod
self.lastModFileTime = centralDirectoryStructure.lastModFileTime
self.lastModFileDate = centralDirectoryStructure.lastModFileDate
self.crc32 = centralDirectoryStructure.crc32
self.compressedSize = centralDirectoryStructure.compressedSize
self.uncompressedSize = centralDirectoryStructure.uncompressedSize
self.fileNameLength = centralDirectoryStructure.fileNameLength
self.fileCommentLength = centralDirectoryStructure.fileCommentLength
self.diskNumberStart = centralDirectoryStructure.diskNumberStart
self.internalFileAttributes = centralDirectoryStructure.internalFileAttributes
self.externalFileAttributes = centralDirectoryStructure.externalFileAttributes
self.fileNameData = centralDirectoryStructure.fileNameData
self.fileCommentData = centralDirectoryStructure.fileCommentData
if let zip64ExtendedInformation = Entry.ZIP64ExtendedInformation.scanForZIP64Field(in: self.extraFieldData,
fields: self.validFields) {
self.extraFields = [zip64ExtendedInformation]
}
}
}
extension Entry.CentralDirectoryStructure {
var effectiveCompressedSize: UInt64 {
if self.isZIP64, let compressedSize = self.zip64ExtendedInformation?.compressedSize, compressedSize > 0 {
return compressedSize
}
return UInt64(compressedSize)
}
var effectiveUncompressedSize: UInt64 {
if self.isZIP64, let uncompressedSize = self.zip64ExtendedInformation?.uncompressedSize, uncompressedSize > 0 {
return uncompressedSize
}
return UInt64(uncompressedSize)
}
var effectiveRelativeOffsetOfLocalHeader: UInt64 {
if self.isZIP64, let offset = self.zip64ExtendedInformation?.relativeOffsetOfLocalHeader, offset > 0 {
return offset
}
return UInt64(relativeOffsetOfLocalHeader)
}
}
extension String.Encoding {
static let codepage437: Self = {
let dosLatinUS = 0x400
let dosLatinUSEncoding = CFStringEncoding(dosLatinUS)
let dosLatinUSStringEncoding = CFStringConvertEncodingToNSStringEncoding(dosLatinUSEncoding)
return String.Encoding(rawValue: dosLatinUSStringEncoding)
}()
}
extension String {
init(pathData: Data, encoding: String.Encoding) {
#if os(Linux)
if encoding == .codepage437 {
self.init()
for byte in pathData {
self.unicodeScalars.append(Self.cp437Lookup[Int(byte)])
}
return
}
#endif
self = String(data: pathData, encoding: encoding) ?? ""
}
#if os(Linux)
// Source: https://en.wikipedia.org/wiki/Code_page_437#Character_set
private static let cp437Lookup: [UnicodeScalar] = [
0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
0x2261, 0x00B1, 0x2265, 0x2264, 0x2329, 0x2321, 0x00F7, 0x2248,
0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
].map { UnicodeScalar($0)! }
#endif
}