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

163 lines
6.2 KiB
Swift

//
// Entry+ZIP64.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
protocol ExtensibleDataField {
var headerID: UInt16 { get }
var dataSize: UInt16 { get }
}
extension Entry {
enum EntryError: Error {
case missingPermissionsAttributeError
case missingModificationDateAttributeError
}
struct ZIP64ExtendedInformation: ExtensibleDataField {
let headerID: UInt16 = ExtraFieldHeaderID.zip64ExtendedInformation.rawValue
let dataSize: UInt16
static let headerSize: UInt16 = 4
let uncompressedSize: UInt64
let compressedSize: UInt64
let relativeOffsetOfLocalHeader: UInt64
let diskNumberStart: UInt32
}
var zip64ExtendedInformation: ZIP64ExtendedInformation? {
self.centralDirectoryStructure.zip64ExtendedInformation
}
}
typealias Field = Entry.ZIP64ExtendedInformation.Field
extension Entry.LocalFileHeader {
var validFields: [Field] {
var fields: [Field] = []
if self.uncompressedSize == .max { fields.append(.uncompressedSize) }
if self.compressedSize == .max { fields.append(.compressedSize) }
return fields
}
}
extension Entry.CentralDirectoryStructure {
var validFields: [Field] {
var fields: [Field] = []
if self.uncompressedSize == .max { fields.append(.uncompressedSize) }
if self.compressedSize == .max { fields.append(.compressedSize) }
if self.relativeOffsetOfLocalHeader == .max { fields.append(.relativeOffsetOfLocalHeader) }
if self.diskNumberStart == .max { fields.append(.diskNumberStart) }
return fields
}
var zip64ExtendedInformation: Entry.ZIP64ExtendedInformation? {
self.extraFields?.compactMap { $0 as? Entry.ZIP64ExtendedInformation }.first
}
}
extension Entry.ZIP64ExtendedInformation {
enum Field {
case uncompressedSize
case compressedSize
case relativeOffsetOfLocalHeader
case diskNumberStart
var size: Int {
switch self {
case .uncompressedSize, .compressedSize, .relativeOffsetOfLocalHeader:
return 8
case .diskNumberStart:
return 4
}
}
}
var data: Data {
var headerID = self.headerID
var dataSize = self.dataSize
var uncompressedSize = self.uncompressedSize
var compressedSize = self.compressedSize
var relativeOffsetOfLFH = self.relativeOffsetOfLocalHeader
var diskNumberStart = self.diskNumberStart
var data = Data()
withUnsafePointer(to: &headerID, { data.append(UnsafeBufferPointer(start: $0, count: 1))})
withUnsafePointer(to: &dataSize, { data.append(UnsafeBufferPointer(start: $0, count: 1))})
if uncompressedSize != 0 || compressedSize != 0 {
withUnsafePointer(to: &uncompressedSize, { data.append(UnsafeBufferPointer(start: $0, count: 1))})
withUnsafePointer(to: &compressedSize, { data.append(UnsafeBufferPointer(start: $0, count: 1))})
}
if relativeOffsetOfLocalHeader != 0 {
withUnsafePointer(to: &relativeOffsetOfLFH, { data.append(UnsafeBufferPointer(start: $0, count: 1))})
}
if diskNumberStart != 0 {
withUnsafePointer(to: &diskNumberStart, { data.append(UnsafeBufferPointer(start: $0, count: 1))})
}
return data
}
init?(data: Data, fields: [Field]) {
let headerLength = 4
guard fields.reduce(0, { $0 + $1.size }) + headerLength == data.count else { return nil }
var readOffset = headerLength
func value<T>(of field: Field) -> T where T: BinaryInteger {
if fields.contains(field), readOffset + field.size <= data.count {
defer { readOffset += MemoryLayout<T>.size }
return data.scanValue(start: readOffset)
} else {
return 0
}
}
self.dataSize = data.scanValue(start: 2)
self.uncompressedSize = value(of: .uncompressedSize)
self.compressedSize = value(of: .compressedSize)
self.relativeOffsetOfLocalHeader = value(of: .relativeOffsetOfLocalHeader)
self.diskNumberStart = value(of: .diskNumberStart)
}
init?(zip64ExtendedInformation: Entry.ZIP64ExtendedInformation?, offset: UInt64) {
// Only used when removing entry, if no ZIP64 extended information exists,
// then this information will not be newly added either
guard let existingInfo = zip64ExtendedInformation else { return nil }
relativeOffsetOfLocalHeader = offset >= maxOffsetOfLocalFileHeader ? offset : 0
uncompressedSize = existingInfo.uncompressedSize
compressedSize = existingInfo.compressedSize
diskNumberStart = existingInfo.diskNumberStart
let tempDataSize = [relativeOffsetOfLocalHeader, uncompressedSize, compressedSize]
.filter { $0 != 0 }
.reduce(UInt16(0), { $0 + UInt16(MemoryLayout.size(ofValue: $1))})
dataSize = tempDataSize + (diskNumberStart > 0 ? UInt16(MemoryLayout.size(ofValue: diskNumberStart)) : 0)
if dataSize == 0 { return nil }
}
static func scanForZIP64Field(in data: Data, fields: [Field]) -> Entry.ZIP64ExtendedInformation? {
guard data.isEmpty == false else { return nil }
var offset = 0
var headerID: UInt16
var dataSize: UInt16
let extraFieldLength = data.count
let headerSize = Int(Entry.ZIP64ExtendedInformation.headerSize)
while offset < extraFieldLength - headerSize {
headerID = data.scanValue(start: offset)
dataSize = data.scanValue(start: offset + 2)
let nextOffset = offset + headerSize + Int(dataSize)
guard nextOffset <= extraFieldLength else { return nil }
if headerID == ExtraFieldHeaderID.zip64ExtendedInformation.rawValue {
return Entry.ZIP64ExtendedInformation(data: data.subdata(in: offset..<nextOffset), fields: fields)
}
offset = nextOffset
}
return nil
}
}