ZIP Foundation is a library to create, read and modify ZIP archive files.

Related tags

Utility swift zip
Overview

Swift Package Manager compatible Carthage compatible CocoaPods Compatible Platform Twitter

ZIP Foundation is a library to create, read and modify ZIP archive files.
It is written in Swift and based on Apple's libcompression for high performance and energy efficiency.
To learn more about the performance characteristics of the framework, you can read this blog post.

Features

  • Modern Swift API
  • High Performance Compression and Decompression
  • Deterministic Memory Consumption
  • Linux compatibility
  • No 3rd party dependencies (on Apple platforms, zlib on Linux)
  • Comprehensive Unit and Performance Test Coverage
  • Complete Documentation

Requirements

  • iOS 12.0+ / macOS 10.11+ / tvOS 12.0+ / watchOS 2.0+
  • Or Linux with zlib development package
  • Xcode 11.0
  • Swift 4.0

Installation

Swift Package Manager

The Swift Package Manager is a dependency manager integrated with the Swift build system. To learn how to use the Swift Package Manager for your project, please read the official documentation.
To add ZIP Foundation as a dependency, you have to add it to the dependencies of your Package.swift file and refer to that dependency in your target.

", dependencies: [ .package(url: "https://github.com/weichsel/ZIPFoundation.git", .upToNextMajor(from: "0.9.0")) ], targets: [ .target( name: "", dependencies: ["ZIPFoundation"]), ] ) ">
// swift-tools-version:5.0
import PackageDescription
let package = Package(
    name: "",
    dependencies: [
		.package(url: "https://github.com/weichsel/ZIPFoundation.git", .upToNextMajor(from: "0.9.0"))
    ],
    targets: [
        .target(
		name: "",
		dependencies: ["ZIPFoundation"]),
    ]
)

After adding the dependency, you can fetch the library with:

$ swift package resolve

Carthage

Carthage is a decentralized dependency manager.
Installation instructions can be found in the project's README file.

To integrate ZIPFoundation into your Xcode project using Carthage, you have to add it to your Cartfile:

0.9 ">
github "weichsel/ZIPFoundation" ~> 0.9

After adding ZIPFoundation to the Cartfile, you have to fetch the sources by running:

carthage update --no-build

The fetched project has to be integrated into your workspace by dragging ZIPFoundation.xcodeproj to Xcode's Project Navigator. (See official Carhage docs.)

CocoaPods

CocoaPods is a dependency manager for Objective-C and Swift.
To learn more about setting up your project for CocoaPods, please refer to the official documentation.
To integrate ZIP Foundation into your Xcode project using CocoaPods, you have to add it to your project's Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target '' do
    pod 'ZIPFoundation', '~> 0.9'
end

Afterwards, run the following command:

$ pod install

Usage

ZIP Foundation provides two high level methods to zip and unzip items. Both are implemented as extension of FileManager.
The functionality of those methods is modeled after the behavior of the Archive Utility in macOS.

Note: There is a large performance discrepancy between Debug and Release builds of ZIP Foundation.
The main performance bottleneck is the code that calculates CRC32 checksums. This codepath executes slowly when Swift optimizations are turned off (-Onone). To avoid long wait times when debugging code that extracts archives, the skipCRC32 flag can be set. To learn more about the skipCRC32 parameter, please refer to the documentation strings of the Archive.extract and FileManager.unzipItem methods. Skippig CRC32 checks should only be enabled during debugging.

Zipping Files and Directories

To zip a single file you simply pass a file URL representing the item you want to zip and a destination URL to FileManager.zipItem(at sourceURL: URL, to destinationURL: URL):

let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var sourceURL = URL(fileURLWithPath: currentWorkingPath)
sourceURL.appendPathComponent("file.txt")
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("archive.zip")
do {
    try fileManager.zipItem(at: sourceURL, to: destinationURL)
} catch {
    print("Creation of ZIP archive failed with error:\(error)")
}

By default, archives are created without any compression. To create compressed ZIP archives, the optional compressionMethod parameter has to be set to .deflate.
The same method also accepts URLs that represent directory items. In that case, zipItem adds the directory content of sourceURL to the archive.
By default, a root directory entry named after the lastPathComponent of the sourceURL is added to the destination archive. If you don't want to preserve the parent directory of the source in your archive, you can pass shouldKeepParent: false.

Unzipping Archives

To unzip existing archives, you can use FileManager.unzipItem(at sourceURL: URL, to destinationURL: URL).
This recursively extracts all entries within the archive to the destination URL:

let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var sourceURL = URL(fileURLWithPath: currentWorkingPath)
sourceURL.appendPathComponent("archive.zip")
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("directory")
do {
    try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
    try fileManager.unzipItem(at: sourceURL, to: destinationURL)
} catch {
    print("Extraction of ZIP archive failed with error:\(error)")
}

Advanced Usage

ZIP Foundation also allows you to individually access specific entries without the need to extract the whole archive. Additionally it comes with the ability to incrementally update archive contents.

Accessing individual Entries

To gain access to specific ZIP entries, you have to initialize an Archive object with a file URL that represents an existing archive. After doing that, entries can be retrieved via their relative path. Archive conforms to Sequence and therefore supports subscripting:

let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("archive.zip")
guard let archive = Archive(url: archiveURL, accessMode: .read) else  {
    return
}
guard let entry = archive["file.txt"] else {
    return
}
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("out.txt")
do {
    try archive.extract(entry, to: destinationURL)
} catch {
    print("Extracting entry from archive failed with error:\(error)")
}

The extract method accepts optional parameters that allow you to control compression and memory consumption.
You can find detailed information about that parameters in the method's documentation.

Creating Archives

To create a new Archive, pass in a non-existing file URL and AccessMode.create.

let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("newArchive.zip")
guard let archive = Archive(url: archiveURL, accessMode: .create) else  {
    return
}

Adding and Removing Entries

You can add or remove entries to/from archives that have been opened with .create or .update AccessMode. To add an entry from an existing file, you can pass a relative path and a base URL to addEntry. The relative path identifies the entry within the ZIP archive. The relative path and the base URL must form an absolute file URL that points to the file you want to add to the archive:

let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("archive.zip")
guard let archive = Archive(url: archiveURL, accessMode: .update) else  {
    return
}
var fileURL = URL(fileURLWithPath: currentWorkingPath)
fileURL.appendPathComponent("file.txt")
do {
    try archive.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent())
} catch {
    print("Adding entry to ZIP archive failed with error:\(error)")
}

Alternatively, the addEntry(with path: String, fileURL: URL) method can be used to add files that are not sharing a common base directory. The fileURL parameter must contain an absolute file URL that points to a file, symlink or directory on an arbitrary file system location.

The addEntry method accepts several optional parameters that allow you to control compression, memory consumption and file attributes.
You can find detailed information about that parameters in the method's documentation.

To remove an entry, you need a reference to an entry within an archive that you can pass to removeEntry:

guard let entry = archive["file.txt"] else {
    return
}
do {
    try archive.remove(entry)
} catch {
    print("Removing entry from ZIP archive failed with error:\(error)")
}

Closure based Reading and Writing

ZIP Foundation also allows you to consume ZIP entry contents without writing them to the file system. The extract method accepts a closure of type Consumer. This closure is called during extraction until the contents of an entry are exhausted:

try archive.extract(entry, consumer: { (data) in
    print(data.count)
})

The data passed into the closure contains chunks of the current entry. You can control the chunk size of the entry by providing the optional bufferSize parameter.

You can also add entries from an in-memory data source. To do this you have to provide a closure of type Provider to the addEntry method:

Data in // This will be called until `data` is exhausted (3x in this case). return data.subdata(in: position..
let string = "abcdefghijkl"
guard let data = string.data(using: .utf8) else { return }
try? archive.addEntry(with: "fromMemory.txt", type: .file, uncompressedSize: UInt32(string.count), bufferSize: 4, provider: { (position, size) -> Data in
    // This will be called until `data` is exhausted (3x in this case).
    return data.subdata(in: position..<position+size)
})

The closure is called until enough data has been provided to create an entry of uncompressedSize. The closure receives position and size arguments so that you can manage the state of your data source.

In-Memory Archives

Besides closure based reading and writing of file based archives, ZIP Foundation also provides capabilities to process in-memory archives. This allows creation or extraction of archives that only reside in RAM. One use case for this functionality is dynamic creation of ZIP archives that are later sent to a client - without performing any disk IO.

To work with in-memory archives the init(data: Data, accessMode: AccessMode) initializer must be used.
To read or update an in-memory archive, the passed-in data must contain a representation of a valid ZIP archive.
To create an in-memory archive, the data parameter can be omitted:

Data in return data.subdata(in: position..
let string = "Some string!"
guard let archive = Archive(accessMode: .create),
        let data = string.data(using: .utf8) else { return }
    try? archive.addEntry(with: "inMemory.txt", type: .file, uncompressedSize: UInt32(string.count), bufferSize: 4, provider: { (position, size) -> Data in
        return data.subdata(in: position..<position+size)
    })
let archiveData = archive.data

Progress Tracking and Cancellation

All Archive operations take an optional progress parameter. By passing in an instance of Progress, you indicate that you want to track the progress of the current ZIP operation. ZIP Foundation automatically configures the totalUnitCount of the progress object and continuously updates its completedUnitCount.
To get notifications about the completed work of the current operation, you can attach a Key-Value Observer to the fractionCompleted property of your progress object.
The ZIP Foundation FileManager extension methods also accept optional progress parameters. zipItem and unzipItem both automatically create a hierarchy of progress objects that reflect the progress of all items contained in a directory or an archive that contains multiple items.

The cancel() method of Progress can be used to terminate an unfinished ZIP operation. In case of cancelation, the current operation throws an ArchiveError.cancelledOperation exception.

Credits

ZIP Foundation is written and maintained by Thomas Zoechling.
Twitter: @weichsel.

License

ZIP Foundation is released under the MIT License.
See LICENSE for details.

Comments
  • ZIP64 Support

    ZIP64 Support

    The original .ZIP format had a 4 GiB (2^32 bytes) limit, which is no longer relevant on 64 bit architectures and, since version 4.5 of the specification (currently at 6.3.6), has been supplanted by ZIP64, which increases the limits to 16EiB (2^64 bytes).

    ZIPFoundation should be able to handle archives larger than 4 GiB by adopting the ability to work with ZIP64 archives.

    https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT

    help wanted 
    opened by levigroker 20
  • cannot link ZIPFoundation 0.9.x in debug with xcode 12.5

    cannot link ZIPFoundation 0.9.x in debug with xcode 12.5

    Summary

    My app using ZIPFoundation cannot link anymore in debug with xcode 12.5 due to the following error: Undefined symbols for architecture arm64: "Swift._ArrayBuffer._copyContents(initializing: Swift.UnsafeMutableBufferPointer) -> (Swift.IndexingIterator<Swift._ArrayBuffer>, Swift.Int)", referenced from: generic specialization <serialized, Swift._ArrayBuffer<Swift.Int8>> of Swift._copyCollectionToContiguousArray(A) -> Swift.ContiguousArray<A.Element> in AppDelegate.o

    Steps to Reproduce

    i was happily using ZIPFoundation 0.9.9 within my project with xcode12.4. After upgrading to xcode 12.5 i had to recompile ZIPFoundation due to swift compiler changes. After that i recompiled ZIPFoundation i tried to compile my project but could not due to the error above. A few notes:

    1. i tried upgrading to ZF 0.9.12 or even latest git: same error
    2. compiling in release works as expected
    3. removing ZF from my project i managed to compile it in debug, so the problem resides within the linking in debug with ZF

    Expected Results

    successful linking

    Actual Results

    linking error: Undefined symbols for architecture arm64: "Swift._ArrayBuffer._copyContents(initializing: Swift.UnsafeMutableBufferPointer) -> (Swift.IndexingIterator<Swift._ArrayBuffer>, Swift.Int)", referenced from: generic specialization <serialized, Swift._ArrayBuffer<Swift.Int8>> of Swift._copyCollectionToContiguousArray(A) -> Swift.ContiguousArray<A.Element> in AppDelegate.o

    Regression & Version

    xcode 12.5 and ZF 0.9.9 / 0.9.12 / git 2021-05-12

    Related Link

    opened by alienpenguin 17
  • ZIP64 Support

    ZIP64 Support

    Fixes #118

    Changes proposed in this PR

    • Add zip64 structures
      • Archive.Zip64EndOfCentralDirectoryRecord
      • Archive.Zip64EndOfCentralDirectoryLocator
      • Entry.Zip64ExtendedInformation
        • Using in both Local Header File and Central Directory, if uncompressed size or compressed size exceeds the limit, extended information should include both uncompressed and compressed file size fields. (at least in the Local header File)
    • Support writing zip64 file

    Tests performed

    • All unit tests pass and coverage rate is 100%.
    • Going to resolve SwiftLint errors after reviewer finished.
    • In unit tests, the limit size of each field is actually a mock size.
      • Also tested archive files over 4GB on macOS, zip64 files were written out, and they can be opened successfully by double-click.

    Further info for the reviewer

    Note

    1. Since this PR is large enough, the remaining tasks will be handled in the #Next step(See below). Some methods don't work very well enough currently, for this reason some code was commented out in the test cases.
    2. Modified the github action settings temporarily, will revert this change when everything is done.
    3. Code review workflow: https://github.com/weichsel/ZIPFoundation/issues/118#issuecomment-893362662

    Document

    Zip specification: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT

    Overall .ZIP file format:

    [local file header 1]
    [file data 1]
     . 
    .
     .
    [local file header n]
    [file data n]
    [archive extra data record] 
    [central directory header 1]
    .
    .
    .
    [central directory header n]
    [zip64 end of central directory record]
    [zip64 end of central directory locator] 
    [end of central directory record]
    

    Others

    @weichsel Please take a look when you have time, there's no need to hurry~ @billparousis I remember you commented on the zip64 issue, please take look if you still interested in it.

    Next step

    • Unzip zip64 file and reading part.
      • Reading zip64 file
      • Fix entry extraction
      • Fix entry removal
      • DataDescriptor
      • ...
    opened by Ckitakishi 15
  • Memory archive v2

    Memory archive v2

    Sorry about the long radio silence! I finally got around to working on https://github.com/weichsel/ZIPFoundation/pull/78 again, and ended up with a complete redesign that massively simplifies the semantics of the user API.

    Changes proposed in this PR

    • Replace the previous use of fmemopen and open_memstream with a new abstraction MemoryFile which is implemented with funopen on Apple platforms and fopencookie on Linux.
    • Add Archive.init?(data: Data = Data(), accessMode mode: AccessMode, preferredEncoding: String.Encoding? = nil) for in-memory processing of either an existing Data or a new Data to be created.
    • Add Archive.data() -> Data? which for in-memory archives returns the resulting file.
    • So for .read processing, you'd do:
    let archive = Archive(data: myData, accessMode: .read)
    // Usual extraction, as with a file based archive
    
    • For .write and .update processing, there is one additional call:
    let archive = Archive(accessMode: .write)
    // Add entries to archive, as with a file based archive
    if let myData = archive.data {
       //
    }
    
    • On an unrelated matter, I've made Data.compress and Data.uncompress public, because I needed this for a different project (Gzip compression in a web server).

    Tests performed

    • Added unit tests for both the low level MemoryFile functionality and the in-memory Archive abstraction on top of it.
    • Passes on both macOS and Linux

    Further info for the reviewer

    Open Issues

    • The one flaw is that on Linux, this requires passing an extra flag to the Swift compilation. In Swift 5.0 and later, this is possible through Package.swift, and I've added the flag there. On Swift < 5, I believe this functionality is not yet available in the Swift package manager, so you have to manually add -Xcc -D_GNU_SOURCE=1 to the swift invocation. I don't know whether it's possible to add the flag to the Cocoapods build.
    opened by microtherion 14
  • Support in-memory processing of archives

    Support in-memory processing of archives

    Hi! I'm implementing a HTTP server that sometimes needs to extract data from uploaded dictionaries. Since these are ZIP encoded, I was looking for a library, and came across your project. However, I wanted to avoid writing out the uploaded data to a temp file, so I added a new method to your Archive class.

    Changes proposed in this PR

    • Added initializer Archive.init?(data: UnsafeMutableRawPointer, length: Int) that creates a read-only archive from an in-memory pointer instead of an URL.

    Further info for the reviewer

    This is almost certainly not yet in a state where you'd consider merging it; I wanted to send you this to start a discussion on whether you'd be potentially interested in the change. See open issues below.

    Open Issues

    • Code relies on fmemopen which was only introduced in macOS 10.13 / iOS 11.0
    • Code only implements .read mode, because that's my only use case, and buffer handling for the other modes could get tricky.
    • No unit tests yet.
    opened by microtherion 12
  • App crash Thread 21: signal SIGABRT

    App crash Thread 21: signal SIGABRT

    Hello I faced app crash here

    try fileManager.unzipItem(at: archiveURL, to: packDirURL, progress: progress)
    

    when I updated from from 0.9.8 to 0.9.9 version

    opened by alexanderkhitev 11
  • Levi/108

    Levi/108

    Fixes #108

    Changes proposed in this PR

    • Using modern temporary directory mechanism, with fallback if needed.
    • Addresses archive replacement issue when archives reside on different volumes.
    • Removed fileExists check when creating directory as this is contrary to recommended practice.

    Tests performed

    • Added test for temporary directory creation mechanism.
    • Added test for cross-link (separate volume) archive replacement changes.

    Further info for the reviewer

    • Opened up private methods to internal (implicit) for testing purposes.
    • Maintained 100% code coverage and no swiftlint issues as per contribution guidelines.
    opened by levigroker 10
  • Extraction of ZIP archive failed with error:unreadableArchive

    Extraction of ZIP archive failed with error:unreadableArchive

    Summary

    Getting an error Extraction of ZIP archive failed with error:unreadableArchive

    Steps to Reproduce

        let documentsPath1 = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
        let logsPath = documentsPath1.appendingPathComponent("abc.zip")
        let logsPath1 = documentsPath1.appendingPathComponent("abc")
        do
        {
             FileManager.default.createFile(atPath: logsPath!.path, contents: data, attributes: nil)
            try FileManager.default.createDirectory(at: logsPath1!, withIntermediateDirectories: true, attributes: nil)
        }
        catch let error as NSError
        {
            NSLog("Unable to create directory \(error.debugDescription)")
        }
        
        do {
            try FileManager().unzipItem(at: logsPath!, to: logsPath1!)
        } catch {
            print("Extraction of ZIP archive failed with error:\(error)")
        }
    

    Expected Results

    Should unzip the file

    Actual Results

    Regression & Version

    Related Link

    opened by SireeshaSiddineni 10
  • Set `DYLIB_COMPATIBILITY_VERSION` and `DYLIB_CURRENT_VERSION`.

    Set `DYLIB_COMPATIBILITY_VERSION` and `DYLIB_CURRENT_VERSION`.

    IOS-Pods-DFU-Library recently updated its dependency to use ZIPFoundation. In our project we're using Carthage to include that framework, so I also included ZIPFouncation using Carthage. However this results into an error when running the application.

    dyld: Library not loaded: @rpath/ZIPFoundation.framework/ZIPFoundation
      Referenced from: /private/var/containers/Bundle/Application/6768A1C7-5C6E-471B-9969-F84AEFE42EA3/App.app/Frameworks/iOSDFULibrary.framework/iOSDFULibrary
      Reason: Incompatible library version: iOSDFULibrary requires version 1.0.0 or later, but ZIPFoundation provides version 0.0.0
    

    After doing some search I found a similar issue in a different project which was solved by setting DYLIB_COMPATIBILITY_VERSION and DYLIB_CURRENT_VERSION.

    Changes proposed in this PR

    • This PR sets the mentioned project settings to 1 to resolve the mentioned incompatibility when building the project using Carthage.

    Tests performed

    • I don't know what I can or should test, please advise. Our project compiles with this change or fails with the mentioned error without it.

    Further info for the reviewer

    • The mentioned solution comes from here: https://github.com/jpsim/Yams/issues/131
    opened by DevAndArtist 10
  • Fatal Error: NSFileModificationDate not implemented when unzip item on Linux.

    Fatal Error: NSFileModificationDate not implemented when unzip item on Linux.

    Summary

    Fatal error when tried to unzip on linux. Same code, everything works well on macOS, but failed on linux.

    Steps to Reproduce

    try FileManager.default.unzipItem(at: urlToZip, to: urlToDest)
    

    I make a small demo to reproduce this:

    git clone https://gitlab.com/isaacxen/zipFoundationErrorDemo.git
    cd zipFoundationErrorDemo/
    cp example.epub /tmp
    swift package update
    swift run
    

    Expected Results

    successfully unzip.

    Actual Results

    Fatal error: Attribute type not implemented: FileAttributeKey(rawValue: "NSFileModificationDate"): file Foundation/FileManager.swift, line 139
    

    Regression & Version

    ZipFoundation: 0.9.6 Ubuntu 16.04 Swift 4.1

    Related Link

    opened by IsaacXen 10
  • Filename decoding issue for zip files archived by macOS

    Filename decoding issue for zip files archived by macOS

    Summary

    Zip entries filenames are not decoded correctly when reading the content of a zip file produced by macOS.

    Steps to Reproduce

    1. on macOS 10.13.4, make a zip file archive.zip with 2 files "Benoît.txt" and "pic👨‍👩‍👧‍👦🎂.jpg"
    2. Read the path entries of this zip archive on iOS 11.3.1:
    let archive = Archive(url: /urlto:archive.zip/, accessMode: .read)!
    for entry in archive {
      print("entry: \(entry.path)")
    }
    

    Expected Results

    It should log "Benoît.txt" and "pic👨‍👩‍👧‍👦🎂.jpg"

    Actual Results

    It logs "Benoît.txt" and "pic👨‍👩‍👧‍👦🎂.jpg"

    Regression & Version

    • I'm using ZIPFoundation on the master branch (e44ff6b)

    • The workaround to fix this issue is to force to use the UTF-8 encoder in the path getter of the Entry struct in Entry.Swift

    public var path: String {
      ...
      let encoding = String.Encoding.utf8 //isUTF8 ? String.Encoding.utf8 : codepage437
      return String(data: self.centralDirectoryStructure.fileNameData, encoding: encoding) ?? ""
    }
    

    Related Link

    • archive.zip to reproduce this issue. https://www.dropbox.com/s/5tuf7ll4hapwgp1/utfissue.zip?dl=0
    help wanted 
    opened by benoitsan 10
  • Improve Symlink Handling

    Improve Symlink Handling

    Fixes #185 Fixes #62

    Changes proposed in this PR

    • No longer require that symlink destinations are present during unzipping
    • Apply symlink file attributes without link traversal
    opened by weichsel 0
  • Support for Windows and `zlib` portability (NOT an issue)

    Support for Windows and `zlib` portability (NOT an issue)

    I'm just leaving this here for (the dozens!!!) of folks that might be looking for something like ZIPFoundation but need to use it on a non-POSIX platform like Windows.

    I ripped out all the low-level POSIX calls and tweaked the codebase a bit to use high-level Foundation instead of POSIX file descriptors / memory file mapping.

    I also wanted to not rely on zlib being present on the user's OS (non-Apple only, of course), so I instead include the most recent version of zlib from source instead as a replacement for the CZLib shim in the original repo.

    Anyway, here is the repo: ZIPFoundationModern

    opened by gregcotten 0
  • Timestamps of zipped files are UTC

    Timestamps of zipped files are UTC

    Summary

    I zip a file from the iOS filesystem (log file). I then read that file as data and send it as an attachment in an email. When I receive the email, the zip file itself has the correct timestamp, but when I extract the zip, the enclosed file has a timestamp that equates to UTC (i.e. tomorrow for me, US Mountain time)

    Steps to Reproduce

    Create a file in iOS Zip the file Send the file Extract the file Observe the modification time of the file.

    Expected Results

    The timestamp of the extracted file should be in your timezone if originator and recipient are in the same timezone

    Actual Results

    The timezone is "tomorrow", 7 hours ahead, which is my UTC offset.

    Screen Shot 2022-01-11 at 7 37 07 PM

    Regression & Version

    Related Link

    opened by svenyonson 4
  • Attempting to open a non-zip file is slow

    Attempting to open a non-zip file is slow

    Summary

    When trying to open a zip file that isn't actually a zip (e.g., random data), it is very slow. This is because scanForEndOfCentralDirectoryRecord moves back byte-by-byte from the end and attempts to read in the EndOfCentralDirectoryRecord structure each time. If a large file never contains the marker for this record, then it will wind up iterating backward through the entire (potentially massive) file before eventually failing.

    Steps to Reproduce

    import XCTest
    import ZIPFoundation
    
    final class UnzipPerformanceTest: XCTestCase {
        func testUnzipNonzip(count: Int) throws {
            let tmp = URL(fileURLWithPath: "datacount-\(count)", relativeTo: URL(fileURLWithPath: NSTemporaryDirectory()))
            try Data(repeating: 0, count: count).write(to: tmp) // fill the data with zeros
            let archive = Archive(url: tmp, accessMode: .read, preferredEncoding: nil)
            XCTAssertNil(archive, "archive should have been nil")
        }
    
        func testUnzipSmall() throws {
            try testUnzipNonzip(count: 100_000)
        }
    
        func testUnzipMedium() throws {
            try testUnzipNonzip(count: 1_000_000)
        }
    
        func testUnzipLarge() throws {
            try testUnzipNonzip(count: 10_000_000)
        }
    }
    

    Expected Results

    It should not take 10 seconds to fail to open a 10mb non-zip file.

    Actual Results

    Test Suite 'All tests' started at 2022-01-06 22:24:43.620
    Test Suite 'zipbugTests.xctest' started at 2022-01-06 22:24:43.621
    Test Suite 'UnzipPerformanceTest' started at 2022-01-06 22:24:43.621
    Test Case '-[zipbugTests.UnzipPerformanceTest testUnzipLarge]' started.
    Test Case '-[zipbugTests.UnzipPerformanceTest testUnzipLarge]' passed (10.157 seconds).
    Test Case '-[zipbugTests.UnzipPerformanceTest testUnzipMedium]' started.
    Test Case '-[zipbugTests.UnzipPerformanceTest testUnzipMedium]' passed (1.040 seconds).
    Test Case '-[zipbugTests.UnzipPerformanceTest testUnzipSmall]' started.
    Test Case '-[zipbugTests.UnzipPerformanceTest testUnzipSmall]' passed (0.102 seconds).
    Test Suite 'UnzipPerformanceTest' passed at 2022-01-06 22:24:54.921.
    	 Executed 3 tests, with 0 failures (0 unexpected) in 11.299 (11.300) seconds
    Test Suite 'zipbugTests.xctest' passed at 2022-01-06 22:24:54.921.
    	 Executed 3 tests, with 0 failures (0 unexpected) in 11.299 (11.300) seconds
    Test Suite 'All tests' passed at 2022-01-06 22:24:54.921.
    	 Executed 3 tests, with 0 failures (0 unexpected) in 11.299 (11.301) seconds
    Program ended with exit code: 0
    

    Regression & Version

    Related Link

    opened by marcprux 0
  • FileManager.unzipItem doesn't work with some URLs created from relative paths

    FileManager.unzipItem doesn't work with some URLs created from relative paths

    Summary

    If I try to unzip a file (using the FileManager extension) to a URL which was created with a relative path, the extraction fails with an error. Using the relative URL's absoluteURL does work.

    Steps to Reproduce

    1. Create a "relative" URL, such as: URL(fileURLWithPath: "../../Resources", isDirectory: true).
    2. Pass that URL as the to parameter of FileManager.unzipItem (along with a URL to a valid zip file).

    Expected Results

    The contents of the zip file is successfully extracted to the target location.

    Actual Results

    An error is thrown. For example, if the zip file contains a compressed directory called "media", the error is:

    The item couldn’t be opened because the file name “media” is invalid.

    If I get the absoluteURL of the target URL first, it does work.

    Regression & Version

    Tested with 0.9.12.

    Findings

    I think this is caused because the implementation of URL.isContained(in:) (defined in FileManager+ZIP.swift) calls standardized on the URLs first, which outputs the wrong URL when it is created with a relative path. For example:

    fileManager.changeCurrentDirectoryPath("/Users/Robert/Documents")
    
    let relativePath = "../../David/Documents"
    let relativeURL = URL(fileURLWithPath: relativePath)
    
    relativeURL.absoluteString
    // file:///Users/David/Documents
    
    relativeURL.standardized.absoluteString
    // file:///Users/Robert/Documents/David/Documents
    
    opened by Aquilosion 0
Releases(0.9.16)
  • 0.9.16(Dec 7, 2022)

  • 0.9.15(Sep 20, 2022)

    Added

    • Added initial support for building for Android

    Updated

    • Fixed CRC32 calculation for non-final compression streams
    • Fixed evaluation of CRC32 checksums when using FileManager.unzipItem
    Source code(tar.gz)
    Source code(zip)
  • 0.9.14(Sep 19, 2022)

  • 0.9.13(Dec 1, 2021)

    Added

    • Added large file support (ZIP64)

    Updated

    • Fixed an UInt16 overflow when calculating the number of entries
    • Fixed entry removal for in-memory archives
    • Fixed a crash when fopen() fails during archive replacement
    • Improved CRC32 calculation performance via zlib (when available)
    Source code(tar.gz)
    Source code(zip)
  • 0.9.12(Feb 22, 2021)

    Added

    • Added check to disallow removal of entries from readonly archives
    • Added guard against API misuse by providing zero byte buffer sizes

    Updated

    • Fixed an UInt16 overflow when calculating the end of the central directory record
    • Fixed detection of ZIP version required to extract
    • Fixed missing consumer closure call for zero byte entries
    • Fixed erroneous application of .deflate compression on .symlink and .directory entries
    • Improved detection of .directory entries
    • Improved performance when looking up entries via subscripting
    • Improved consistency of URL format used in the Swift package description
    Source code(tar.gz)
    Source code(zip)
  • 0.9.11(Mar 26, 2020)

    Added

    • Read/Write support for in-memory archives

    Updated

    • Fixed a memory safety issue during (de)compression
    • Fixed dangling pointer warnings when serializing ZIP internal structs to Data
    • Fixed missing Swift 5 language version when integrating via CocoaPods
    • Fixed inconsistent usage of the optional preferredEncoding parameter during entry addition
    • Improved documentation for compression settings
    Source code(tar.gz)
    Source code(zip)
  • 0.9.10(Dec 2, 2019)

    Added

    • Optional skipCRC32 parameter to speed up entry reading

    Updated

    • Fixed a race condition during archive creation or extraction
    • Fixed an error when trying to add broken symlinks to an archive
    • Fixed an App Store submission issue by updating the product identifier to use reverse DNS notation
    • Improved CRC32 calculation performance
    • Improved entry replacement performance on separate volumes
    • Improved documentation for closure-based writing
    Source code(tar.gz)
    Source code(zip)
  • 0.9.9(Apr 10, 2019)

    Added

    • Swift 5.0 support
    • Optional preferredEncoding parameter to explicitly configure an encoding for filepaths

    Updated

    • Fixed a library load error related to dylib versioning
    • Fixed a hang during read when decoding small, .deflate compressed entries
    • Improved Linux support
    • Improved test suite on non-Darwin platforms
    Source code(tar.gz)
    Source code(zip)
  • 0.9.8(Jan 21, 2019)

  • 0.9.7(Jan 21, 2019)

    Added

    • App extension support
    • Optional compressionMethod paramter for zipItem:

    Updated

    • Fixed a path traversal attack vulnerability
    • Fixed a crash due to wrong error handling after failed fopen calls

    Removed

    • Temporarily removed the currently unsupported .modificationDate attribute on non-Darwin platforms
    Source code(tar.gz)
    Source code(zip)
  • 0.9.6(Apr 24, 2018)

  • 0.9.5(Feb 9, 2018)

    Added

    • Progress tracking support
    • Operation cancellation support

    Updated

    • Improved performance of CRC32 calculations
    • Improved Linux support
    • Fixed wrong behaviour when using the shouldKeepParent flag
    • Fixed a linker error during archive builds when integrating via Carthage
    Source code(tar.gz)
    Source code(zip)
  • 0.9.4(Dec 8, 2017)

    Updated

    • Fixed a wrong setting for FRAMEWORK_SEARCH_PATHS that interfered with code signing
    • Added a proper value for CURRENT_PROJECT_VERSION to make the framework App Store compliant when using Carthage
    Source code(tar.gz)
    Source code(zip)
  • 0.9.3(Nov 7, 2017)

  • 0.9.2(Aug 5, 2017)

    Updated

    • Changed default POSIX permissions when file attributes are missing
    • Improved docs
    • Fixed a compiler warning when compiling with the latest Xcode 9 beta
    Source code(tar.gz)
    Source code(zip)
  • 0.9.1(Jul 10, 2017)

    Added

    • Optional parameter to skip CRC32 checksum calculation

    Updated

    • Tweaked POSIX buffer sizes to improve IO and compression performance
    • Improved source readability
    • Refined documentation

    Removed

    • Optional parameter to skip decompression during entry retrieval
    Source code(tar.gz)
    Source code(zip)
  • 0.9.0(Jun 27, 2017)

Owner
Thomas Zoechling
I write software for the Mac. Currently helping to build MindNode (macOS, iOS) @IdeasOnCanvas. Also working on Claquette (macOS) https://peakstep.com/claquette
Thomas Zoechling
A Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.

ZamzamKit ZamzamKit is a Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and othe

Zamzam Inc. 261 Dec 15, 2022
The sample implementation of zip-archived document for a macOS AppKit platform.

The sample implementation of zip-archived document for a macOS AppKit platform. You can implement NSDocument-based I/O of archived document in your application like .sketch or .key.

usagimaru 4 Nov 12, 2022
Swifty closures for UIKit and Foundation

Closures is an iOS Framework that adds closure handlers to many of the popular UIKit and Foundation classes. Although this framework is a substitute f

Vinnie Hesener 1.7k Dec 21, 2022
SwiftExtensionKit - SwiftExtensionKit is to contain generic extension helpers for UIKit and Foundation

RichAppz PureSwiftExtensionKit SwiftExtensionKit is to contain generic extension

Rich Mucha 0 Jan 31, 2022
An enhancement built on top of Foundation Framework and XCTest.

Beton is a Swift library built on top of the Foundation framework, that provides an additional layer of functionality, including easy localization, performance test measurement support, and convenience functionality. For us, Beton is primarily, but not exclusively, useful for server-side Swift engineering.

21Gram Consulting 26 Dec 10, 2022
A collection of useful result builders for Swift and Foundation value types

Swift Builders A collection of useful result builders for Swift and Foundation value types. Motivation Arrays, dictionaries, and other collection-base

David Roman 3 Oct 14, 2022
The ISO 8601 period/duration types missing in Foundation

PeriodDuration This library introduces a close equivalent to Java's PeriodDuration, motivated by the lack of support for this standard in Foundation.

David Roman 19 Jun 22, 2022
Ecolande - Application realisé pendant l'Apple foundation Program.

Ecolande Application realisé pendant l'Apple foundation Program. Ecoland est l'application qui a été réalisé pendant l'Apple Foundation Program. Nous

Bilal Larose 1 Dec 31, 2021
Synatax sugar for Measurement of Foundation.

WrappedMeasurement 2022 © Weizhong Yang a.k.a zonble Syntax sugar for NSMeasurement of Foundation. NSMeasurement and NSUnit compose a great tool to le

Weizhong Yang a.k.a zonble 8 Jan 25, 2022
HumanMeasurementSwift - Synatax sugar for Measurement of Foundation

HumanMeasurement 2022 © Weizhong Yang a.k.a zonble Syntax sugar for NSMeasuremen

Weizhong Yang a.k.a zonble 8 Jan 25, 2022
A NEWS app which can be used to read,share and bookmark articles of various categories

Scoop A NEWS App for iOS 14 built using Swift which allow the users to read,bookmark and share news articles. Built using MVC architecture Requirement

Sai Balaji 3 Oct 12, 2022
Protected is a Swift Package that allows you to specify the read and write rights for any type, depending on context by using Phantom types

Protected is a Swift Package that allows you to specify the read and write rights for any type, depending on context by using Phantom types

Mathias Quintero 9 Sep 25, 2022
Read iOS 15 privacy insight '.ndjson' file into your human brain.

Insight Read iOS 15 privacy insight '.ndjson' file into your human brain. Written in SwiftUI. Feature Compile records into app summary Relink app info

Lakr Aream 151 Nov 11, 2022
Validate iOS, Android, and Mac localizations. Find errors in .strings, .stringsdict, and strings.xml files.

Locheck An Xcode and Android localization file validator. Make sure your .strings, .stringsdict, and strings.xml files do not have any errors! What do

Asana 73 Dec 13, 2022
Steps and files needed to reproduce a CSP bug in Safari Web Extensions

CSP Safari bug repro There appears to be a discrepancy between how Safari handles CSP policies for extension pages compared to how other browsers do s

Brian Birtles 0 Nov 6, 2021
Monitor changes to files and directories using kernel event notifications (kqueue) in Swift

SKQueue SKQueue is a Swift libary used to monitor changes to the filesystem. It wraps the part of the kernel event notification interface of libc, kqu

Daniel Pedersen 86 Oct 26, 2022
SwiftUI library to create fully customizable input stepper.

SwiftUI-InputStepper Swift package for creating numerical input stepper. An example of input stepper created with this library It supports long press

Mateusz Budnik 4 Nov 2, 2022
MediaType is a library that can be used to create Media Types in a type-safe manner.

This is a general purpose Swift library for a concept of typed treatment for Media Types. We use this library on clients and servers to speak the same dialect and to enjoy all the comfort strong types provide over raw strings.

21Gram Consulting 79 Jul 19, 2022
Merges a given number of PDF files into one file using the PDFKit framework

Titanium iOS PDF Merge Merges a given number of PDF files into one file using the PDFKit framework Requirements iOS 11+ Titanium SDK 9+ API's Methods

Hans Knöchel 6 Jan 26, 2022