Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions Sources/ZIPFoundation/Archive+ZIP64.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ import Foundation
let zip64EOCDRecordStructSignature = 0x06064b50
let zip64EOCDLocatorStructSignature = 0x07064b50

enum ExtraFieldHeaderID: UInt16 {
case zip64ExtendedInformation = 0x0001
}

extension Archive {
struct ZIP64EndOfCentralDirectory {
let record: ZIP64EndOfCentralDirectoryRecord
Expand Down
5 changes: 5 additions & 0 deletions Sources/ZIPFoundation/Archive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ public final class Archive: Sequence {
case v45 = 45
}

enum ExtraFieldHeaderID: UInt16 {
case zip64ExtendedInformation = 0x0001
case infoZIPUnicodePath = 0x7075
}

struct EndOfCentralDirectoryRecord: DataSerializable {
let endOfCentralDirectorySignature = UInt32(endOfCentralDirectoryStructSignature)
let numberOfDisk: UInt16
Expand Down
62 changes: 62 additions & 0 deletions Sources/ZIPFoundation/Entry+InfoZIP.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// Entry+InfoZIP.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

extension Entry {

struct InfoZIPUnicodePath: ExtensibleDataField {
var headerID: UInt16 { Archive.ExtraFieldHeaderID.infoZIPUnicodePath.rawValue }
let dataSize: UInt16
let version: UInt8
let nameCRC32: UInt32
let unicodeName: Data
}

var infoZIPExtraField: InfoZIPUnicodePath? {
let extraField = self.localFileHeader.extraFields?.first { $0 is InfoZIPUnicodePath }
return extraField as? InfoZIPUnicodePath
}
}

extension Entry.InfoZIPUnicodePath {

static func scanForUnicodePath(in data: Data) -> Entry.InfoZIPUnicodePath? {
guard data.isEmpty == false else { return nil }
var offset = 0
var headerID: UInt16
var dataSize: UInt16
let extraFieldLength = data.count
let headerSize = 4

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 == Archive.ExtraFieldHeaderID.infoZIPUnicodePath.rawValue {
let fieldData = data.subdata(in: offset..<nextOffset)
let version: UInt8 = fieldData.scanValue(start: 4)
let nameCRC32: UInt32 = fieldData.scanValue(start: 5)
let unicodeNameData = fieldData.subdata(in: 9..<fieldData.count)

return Entry.InfoZIPUnicodePath(
dataSize: dataSize,
version: version,
nameCRC32: nameCRC32,
unicodeName: unicodeNameData
)
}
offset = nextOffset
}
return nil
}
}
7 changes: 6 additions & 1 deletion Sources/ZIPFoundation/Entry+Serialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,15 @@ extension Entry.LocalFileHeader {
subRangeStart += Int(self.fileNameLength)
subRangeEnd = subRangeStart + Int(self.extraFieldLength)
self.extraFieldData = additionalData.subdata(in: subRangeStart..<subRangeEnd)
var extraFields: [ExtensibleDataField] = []
if let zip64ExtendedInformation = Entry.ZIP64ExtendedInformation.scanForZIP64Field(in: self.extraFieldData,
fields: self.validFields) {
self.extraFields = [zip64ExtendedInformation]
extraFields.append(zip64ExtendedInformation)
}
if let infoZIPUnicodePath = Entry.InfoZIPUnicodePath.scanForUnicodePath(in: self.extraFieldData) {
extraFields.append(infoZIPUnicodePath)
}
self.extraFields = extraFields
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/ZIPFoundation/Entry+ZIP64.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension Entry {
}

struct ZIP64ExtendedInformation: ExtensibleDataField {
let headerID: UInt16 = ExtraFieldHeaderID.zip64ExtendedInformation.rawValue
let headerID: UInt16 = Archive.ExtraFieldHeaderID.zip64ExtendedInformation.rawValue
let dataSize: UInt16
static let headerSize: UInt16 = 4
let uncompressedSize: UInt64
Expand Down Expand Up @@ -152,7 +152,7 @@ extension Entry.ZIP64ExtendedInformation {
dataSize = data.scanValue(start: offset + 2)
let nextOffset = offset + headerSize + Int(dataSize)
guard nextOffset <= extraFieldLength else { return nil }
if headerID == ExtraFieldHeaderID.zip64ExtendedInformation.rawValue {
if headerID == Archive.ExtraFieldHeaderID.zip64ExtendedInformation.rawValue {
return Entry.ZIP64ExtendedInformation(data: data.subdata(in: offset..<nextOffset), fields: fields)
}
offset = nextOffset
Expand Down
8 changes: 8 additions & 0 deletions Sources/ZIPFoundation/Entry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ public struct Entry: Equatable {
}
/// The `path` of the receiver within a ZIP `Archive`.
public var path: String {
// Read Info-ZIP Unicode Path extra field if present
if let infoZIPExtraField = self.infoZIPExtraField {
// Validate CRC32 before using the Unicode name
let originalFilenameCRC32 = self.centralDirectoryStructure.fileNameData.crc32(checksum: 0)
if infoZIPExtraField.nameCRC32 == originalFilenameCRC32 {
return String(pathData: infoZIPExtraField.unicodeName, encoding: .utf8)
}
}
let encoding = self.centralDirectoryStructure.usesUTF8PathEncoding ? String.Encoding.utf8 : .codepage437
return self.path(using: encoding)
}
Expand Down
Binary file not shown.
4 changes: 3 additions & 1 deletion Tests/ZIPFoundationTests/ZIPFoundationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ extension ZIPFoundationTests {
("testExtractCompressedZIP64Entries", testExtractCompressedZIP64Entries),
("testExtractEntryWithZIP64DataDescriptor", testExtractEntryWithZIP64DataDescriptor),
("testUnzipSymlink", testUnzipSymlink),
("testUnzipCompressedSymlink", testUnzipCompressedSymlink)
("testUnzipCompressedSymlink", testUnzipCompressedSymlink),
("testEntryScanForInfoZIP", testEntryScanForInfoZIP),
("testInfoZIPUnicodePath", testInfoZIPUnicodePath)
]
}

Expand Down
35 changes: 35 additions & 0 deletions Tests/ZIPFoundationTests/ZipFoundationEntryTests+InfoZip.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// ZIPFoundationEntryTests+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 XCTest
@testable import ZIPFoundation

extension ZIPFoundationTests {

func testEntryScanForInfoZIP() {
let extraFieldBytesWithInfoZIP: [UInt8] = [0x75, 0x70, 0x37, 0x00, 0x01, 0xa3, 0xdb, 0xf3, 0x25,
0xe4, 0xbf, 0xa1, 0xe9, 0x93, 0xb6, 0xe8, 0xa7, 0x84,
0xe7, 0xab, 0xa0, 0xe3, 0x80, 0x94, 0x32, 0x30, 0x32,
0x35, 0xe3, 0x80, 0x95, 0x32, 0x37, 0x34, 0xe5, 0x8f,
0xb7, 0xef, 0xbc, 0x88, 0xe9, 0x99, 0x84, 0xe4, 0xbb,
0xb6, 0xef, 0xbc, 0x89, 0x5f, 0x54, 0x65, 0x73, 0x74,
0x2e, 0x64, 0x6f, 0x63, 0x78]
let infoZIP = Entry.InfoZIPUnicodePath.scanForUnicodePath(in: Data(extraFieldBytesWithInfoZIP))
XCTAssertEqual(infoZIP?.headerID, Archive.ExtraFieldHeaderID.infoZIPUnicodePath.rawValue)
XCTAssertNotNil(infoZIP)
}

func testInfoZIPUnicodePath() {
let fileManager = FileManager()
let archive = self.archive(for: #function, mode: .read)
let destinationURL = self.createDirectory(for: #function)
XCTAssertNoThrow(try fileManager.unzipItem(at: archive.url, to: destinationURL))
}
}
8 changes: 8 additions & 0 deletions ZIPFoundation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
59248AA226B6EC770078ABDA /* ZIPFoundationErrorConditionTests+ZIP64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59248AA126B6EC770078ABDA /* ZIPFoundationErrorConditionTests+ZIP64.swift */; };
5968EBED26D692360037EA62 /* ZIPFoundationReadingTests+ZIP64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5968EBEC26D692360037EA62 /* ZIPFoundationReadingTests+ZIP64.swift */; };
5968EBF126D695AD0037EA62 /* ZIPFoundationFileManagerTests+ZIP64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5968EBF026D695AD0037EA62 /* ZIPFoundationFileManagerTests+ZIP64.swift */; };
82BD5DB42F1112F300DA5764 /* Entry+InfoZIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82BD5DB32F1112D900DA5764 /* Entry+InfoZIP.swift */; };
82BD5DB62F11142800DA5764 /* ZipFoundationEntryTests+InfoZip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82BD5DB52F11141E00DA5764 /* ZipFoundationEntryTests+InfoZip.swift */; };
95B5A3D42366731B00D4D8FD /* Archive+MemoryFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B5A3D32366731B00D4D8FD /* Archive+MemoryFile.swift */; };
95B5A3D72367999900D4D8FD /* ZIPFoundationMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B5A3D52367983A00D4D8FD /* ZIPFoundationMemoryTests.swift */; };
BA0CE5042746AF0C004D8DD4 /* Archive+WritingDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0CE5032746AF0C004D8DD4 /* Archive+WritingDeprecated.swift */; };
Expand Down Expand Up @@ -82,6 +84,8 @@
59248AA126B6EC770078ABDA /* ZIPFoundationErrorConditionTests+ZIP64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZIPFoundationErrorConditionTests+ZIP64.swift"; sourceTree = "<group>"; };
5968EBEC26D692360037EA62 /* ZIPFoundationReadingTests+ZIP64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZIPFoundationReadingTests+ZIP64.swift"; sourceTree = "<group>"; };
5968EBF026D695AD0037EA62 /* ZIPFoundationFileManagerTests+ZIP64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZIPFoundationFileManagerTests+ZIP64.swift"; sourceTree = "<group>"; };
82BD5DB32F1112D900DA5764 /* Entry+InfoZIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Entry+InfoZIP.swift"; sourceTree = "<group>"; };
82BD5DB52F11141E00DA5764 /* ZipFoundationEntryTests+InfoZip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZipFoundationEntryTests+InfoZip.swift"; sourceTree = "<group>"; };
95B5A3D32366731B00D4D8FD /* Archive+MemoryFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Archive+MemoryFile.swift"; sourceTree = "<group>"; };
95B5A3D52367983A00D4D8FD /* ZIPFoundationMemoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZIPFoundationMemoryTests.swift; sourceTree = "<group>"; };
BA0CE5032746AF0C004D8DD4 /* Archive+WritingDeprecated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Archive+WritingDeprecated.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -162,6 +166,7 @@
OBJ_18 /* ZIPFoundationDataSerializationTests.swift */,
OBJ_19 /* ZIPFoundationEntryTests.swift */,
590A7DAD26B0F95E00CBEFB4 /* ZIPFoundationEntryTests+ZIP64.swift */,
82BD5DB52F11141E00DA5764 /* ZipFoundationEntryTests+InfoZip.swift */,
BAAF13D925CC140300070A95 /* ZIPFoundationErrorConditionTests.swift */,
59248AA126B6EC770078ABDA /* ZIPFoundationErrorConditionTests+ZIP64.swift */,
BA0F721F29756590006DD5DB /* ZIPFoundationFileAttributeTests.swift */,
Expand Down Expand Up @@ -229,6 +234,7 @@
OBJ_14 /* Entry.swift */,
BACE20BC26F7D545003BA312 /* Entry+Serialization.swift */,
590A7DA726B0F91E00CBEFB4 /* Entry+ZIP64.swift */,
82BD5DB32F1112D900DA5764 /* Entry+InfoZIP.swift */,
OBJ_15 /* FileManager+ZIP.swift */,
BA85B4F32991614E003F2748 /* FileManager+ZIPDeprecated.swift */,
BA643D79264811FB00018273 /* URL+ZIP.swift */,
Expand Down Expand Up @@ -346,6 +352,7 @@
OBJ_36 /* ZIPFoundationPerformanceTests.swift in Sources */,
OBJ_37 /* ZIPFoundationReadingTests.swift in Sources */,
95B5A3D72367999900D4D8FD /* ZIPFoundationMemoryTests.swift in Sources */,
82BD5DB62F11142800DA5764 /* ZipFoundationEntryTests+InfoZip.swift in Sources */,
59248AA226B6EC770078ABDA /* ZIPFoundationErrorConditionTests+ZIP64.swift in Sources */,
OBJ_38 /* ZIPFoundationTests.swift in Sources */,
OBJ_39 /* ZIPFoundationWritingTests.swift in Sources */,
Expand All @@ -360,6 +367,7 @@
BA85B4F2299160E4003F2748 /* Archive+Deprecated.swift in Sources */,
BA0CE5062746AF1A004D8DD4 /* Data+CompressionDeprecated.swift in Sources */,
OBJ_49 /* Archive+Writing.swift in Sources */,
82BD5DB42F1112F300DA5764 /* Entry+InfoZIP.swift in Sources */,
OBJ_50 /* Archive.swift in Sources */,
BACE20B826F7CE6C003BA312 /* Archive+BackingConfiguration.swift in Sources */,
OBJ_51 /* Data+Compression.swift in Sources */,
Expand Down