Files
2026-01-31 15:30:24 +08:00

193 lines
6.6 KiB
Swift

// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
import Foundation
import BitByteData
/// Provides access to information about an entry from the ZIP container.
public struct ZipEntryInfo: ContainerEntryInfo {
// MARK: ContainerEntryInfo
public let name: String
public let size: Int?
public let type: ContainerEntryType
/**
Entry's last access time (`nil`, if not available).
Set from different sources in the following preference order:
1. Extended timestamp extra field (most common on UNIX-like systems).
2. NTFS extra field.
*/
public let accessTime: Date?
/**
Entry's creation time (`nil`, if not available).
Set from different sources in the following preference order:
1. Extended timestamp extra field (most common on UNIX-like systems).
2. NTFS extra field.
*/
public let creationTime: Date?
/**
Entry's last modification time.
Set from different sources in the following preference order:
1. Extended timestamp extra field (most common on UNIX-like systems).
2. NTFS extra field.
3. ZIP container's own storage (in Central Directory entry).
*/
public let modificationTime: Date?
/**
Entry's permissions in POSIX format.
May have meaningless value if origin file system's attributes weren't POSIX compatible.
*/
public let permissions: Permissions?
// MARK: ZIP specific
/// Entry's comment.
public let comment: String
/**
Entry's external file attributes. ZIP internal property.
May be useful when origin file system's attributes weren't POSIX compatible.
*/
public let externalFileAttributes: UInt32
/// Entry's attributes in DOS format.
public let dosAttributes: DosAttributes?
/// True, if entry is likely to be text or ASCII file.
public let isTextFile: Bool
/// File system type of container's origin.
public let fileSystemType: FileSystemType
/// Entry's compression method.
public let compressionMethod: CompressionMethod
/**
ID of entry's owner.
Set from different sources in the following preference order, if possible:
1. Info-ZIP New Unix extra field.
2. Info-ZIP Unix extra field.
*/
public let ownerID: Int?
/**
ID of the group of entry's owner.
Set from different sources in the following preference order, if possible:
1. Info-ZIP New Unix extra field.
2. Info-ZIP Unix extra field.
*/
public let groupID: Int?
/**
Entry's custom extra fields from both Central Directory and Local Header.
- Note: No particular order of extra fields is guaranteed.
*/
public let customExtraFields: [ZipExtraField]
/// CRC32 of entry's data.
public let crc: UInt32
init(_ byteReader: LittleEndianByteReader, _ cdEntry: ZipCentralDirectoryEntry, _ localHeader: ZipLocalHeader,
_ hasDataDescriptor: Bool) {
self.name = cdEntry.fileName
// Set Modification Time.
if let mtime = cdEntry.extendedTimestampExtraField?.mtime {
// Extended Timestamp extra field.
self.modificationTime = Date(timeIntervalSince1970: TimeInterval(mtime))
} else if let mtime = cdEntry.ntfsExtraField?.mtime {
// NTFS extra field.
self.modificationTime = Date(mtime)
} else {
// Native ZIP 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
self.modificationTime = DateComponents(calendar: Calendar.current, timeZone: TimeZone.current,
year: year, month: month, day: day,
hour: hours, minute: minutes, second: seconds).date
}
// Set Creation Time.
if let ctime = localHeader.extendedTimestampExtraField?.ctime {
// Extended Timestamp extra field.
self.creationTime = Date(timeIntervalSince1970: TimeInterval(ctime))
} else if let ctime = cdEntry.ntfsExtraField?.ctime {
// NTFS extra field.
self.creationTime = Date(ctime)
} else {
self.creationTime = nil
}
// Set Creation Time.
if let atime = localHeader.extendedTimestampExtraField?.atime {
// Extended Timestamp extra field.
self.accessTime = Date(timeIntervalSince1970: TimeInterval(atime))
} else if let atime = cdEntry.ntfsExtraField?.atime {
// NTFS extra field.
self.accessTime = Date(atime)
} else {
self.accessTime = nil
}
self.size = (hasDataDescriptor ? cdEntry.uncompSize : localHeader.uncompSize).toInt()
self.externalFileAttributes = cdEntry.externalFileAttributes
self.permissions = Permissions(rawValue: (0x0FFF0000 & cdEntry.externalFileAttributes) >> 16)
self.dosAttributes = DosAttributes(rawValue: 0xFF & cdEntry.externalFileAttributes)
// Set entry type.
if let unixType = ContainerEntryType((0xF0000000 & cdEntry.externalFileAttributes) >> 16) {
self.type = unixType
} else if let dosAttributes = self.dosAttributes {
if dosAttributes.contains(.directory) {
self.type = .directory
} else {
self.type = .regular
}
} else if size == 0 && cdEntry.fileName.last == "/" {
self.type = .directory
} else {
self.type = .regular
}
self.comment = cdEntry.fileComment
self.isTextFile = cdEntry.internalFileAttributes & 0x1 != 0
self.fileSystemType = FileSystemType(cdEntry.versionMadeBy)
self.compressionMethod = CompressionMethod(localHeader.compressionMethod)
self.ownerID = localHeader.infoZipNewUnixExtraField?.uid ?? localHeader.infoZipUnixExtraField?.uid
self.groupID = localHeader.infoZipNewUnixExtraField?.gid ?? localHeader.infoZipUnixExtraField?.gid
self.crc = hasDataDescriptor ? cdEntry.crc32 : localHeader.crc32
// Custom extra fields.
var customExtraFields = cdEntry.customExtraFields
customExtraFields.append(contentsOf: localHeader.customExtraFields)
self.customExtraFields = customExtraFields
}
}