Effortless ZIP Handling in Swift

Related tags

Files 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.

// swift-tools-version:5.0
import PackageDescription
let package = Package(
    name: "<Your Product Name>",
    dependencies: [
		.package(url: "https://github.com/weichsel/ZIPFoundation.git", .upToNextMajor(from: "0.9.0"))
    ],
    targets: [
        .target(
		name: "<Your 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:

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 '<Your Target Name>' 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:

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:

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
zip file I/O library for iOS, macOS and tvOS

ZipZap is a zip file I/O library for iOS, macOS and tvOS. The zip file is an ideal container for compound Objective-C documents. Zip files are widely

Glen Low 1.2k Dec 27, 2022
FileKit is a Swift framework that allows for simple and expressive file management.

FileKit is a Swift framework that allows for simple and expressive file management.

Nikolai Vazquez 2.2k Jan 7, 2023
FileManager replacement for Local, iCloud and Remote (WebDAV/FTP/Dropbox/OneDrive) files -- Swift

This Swift library provide a swifty way to deal with local and remote files and directories in a unified way. This library provides implementaion of W

Amir Abbas Mousavian 890 Jan 6, 2023
File management and path analysis for Swift

Pathos offers cross-platform virtual file system APIs for Swift. Pathos is implement from ground-up with on each OS's native API. It has zero dependen

Daniel Duan 104 Nov 19, 2022
Swift framework for zipping and unzipping files.

Zip A Swift framework for zipping and unzipping files. Simple and quick to use. Built on top of minizip. Usage Import Zip at the top of the Swift file

Roy Marmelstein 2.3k Dec 30, 2022
Finder-style iOS file browser written in Swift

FileBrowser iOS Finder-style file browser in Swift 4.0 with search, file previews and 3D touch. Simple and quick to use. Features ✨ Features ?? Browse

Roy Marmelstein 1.5k Dec 16, 2022
Swift framework to connect SMB2/3 shares

AMSMB2 This is small Swift library for iOS, macOS and tvOS which wraps libsmb2 and allows to connect a SMB2/3 share and do file operation. Install Coc

Amir Abbas Mousavian 157 Dec 19, 2022
ZIP Foundation is a library to create, read and modify ZIP archive files.

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 performa

Thomas Zoechling 1.9k Dec 27, 2022
Effortless path operations in Swift

PathKit Effortless path operations in Swift. Usage let path = Path("/usr/bin/swift") Joining paths let path = Path("/usr/bin") + Path("swift") Determi

Kyle Fuller 1.4k Jan 5, 2023
Effortless emoji-querying in Swift

EmojiKit EmojiKit is a simple emoji-querying framework in Swift. It is used in Paste, an Emoji Search app in the App Store. Installation If you’re usi

Dasmer Singh 93 Nov 12, 2022
A type-safe, protocol-based, pure Swift database offering effortless persistence of any object

There are many libraries out there that aims to help developers easily create and use SQLite databases. Unfortunately developers still have to get bogged down in simple tasks such as writing table definitions and SQL queries. SwiftyDB automatically handles everything you don't want to spend your time doing.

Øyvind Grimnes 489 Sep 9, 2022
Effortless modular dependency injection for Swift.

Inject Effortless modular dependency injection for Swift. Sometimes during the app development process we need to replace instances of classes or acto

Maxim Bazarov 40 Dec 6, 2022
CachyKit - A Caching Library is written in Swift that can cache JSON, Image, Zip or AnyObject with expiry date/TTYL and force refresh.

Nice threadsafe expirable cache management that can cache any object. Supports fetching from server, single object expire date, UIImageView loading etc.

Sadman Samee 122 Dec 28, 2022
Zip - A Swift framework for zipping and unzipping files. Simple and quick to use. Built on top of minizip.

Zip A Swift framework for zipping and unzipping files. Simple and quick to use. Built on top of minizip. Usage Import Zip at the top of the Swift file

Roy Marmelstein 2.3k Jan 3, 2023
Create xcframework zip archive for Swift binary package.

xczip xczip - create xcframework zip archive for swift binary package. Creates an xcframework archive with a stable checksum that does not change when

Dmitriy Borovikov 0 Jan 8, 2022
zip file I/O library for iOS, macOS and tvOS

ZipZap is a zip file I/O library for iOS, macOS and tvOS. The zip file is an ideal container for compound Objective-C documents. Zip files are widely

Glen Low 1.2k Dec 27, 2022
Mephisto - A command line tool to convert Comic Book Zip archives to PDF and share them over AirDrop

mephisto A command line tool written in Swift to convert Comic Book Zip archives

null 0 Feb 7, 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
WebDomHandling - A Swift Package for handling JavaScript code between WebKit and Swift implemented by WebKit

WebDomHandling A Swift Package for handling JavaScript code between WebKit and S

null 0 Jan 23, 2022
HEX color handling as an extension for UIColor. Written in Swift.

SwiftHEXColors HEX color handling as an extension for UIColor. Written in Swift.

Thi Doãn 692 Dec 22, 2022