aboutsummaryrefslogtreecommitdiffstats
path: root/WireGuard/WireGuard/ZipArchive/ZipArchive.swift
blob: 9e28dc299d9a1a000d2a19f5077aec56c43b5bb2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.

import Foundation

enum ZipArchiveError: WireGuardAppError {
    case cantOpenInputZipFile
    case cantOpenOutputZipFileForWriting
    case badArchive

    var alertText: AlertText {
        switch self {
        case .cantOpenInputZipFile:
            return (tr("alertCantOpenInputZipFileTitle"), tr("alertCantOpenInputZipFileMessage"))
        case .cantOpenOutputZipFileForWriting:
            return (tr("alertCantOpenOutputZipFileForWritingTitle"), tr("alertCantOpenOutputZipFileForWritingMessage"))
        case .badArchive:
            return (tr("alertBadArchiveTitle"), tr("alertBadArchiveMessage"))
        }
    }
}

class ZipArchive {

    static func archive(inputs: [(fileName: String, contents: Data)], to destinationURL: URL) throws {
        let destinationPath = destinationURL.path
        guard let zipFile = zipOpen(destinationPath, APPEND_STATUS_CREATE) else {
            throw ZipArchiveError.cantOpenOutputZipFileForWriting
        }
        for input in inputs {
            let fileName = input.fileName
            let contents = input.contents
            zipOpenNewFileInZip(zipFile, fileName.cString(using: .utf8), nil, nil, 0, nil, 0, nil, Z_DEFLATED, Z_DEFAULT_COMPRESSION)
            contents.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) -> Void in
                zipWriteInFileInZip(zipFile, UnsafeRawPointer(ptr), UInt32(contents.count))
            }
            zipCloseFileInZip(zipFile)
        }
        zipClose(zipFile, nil)
    }

    static func unarchive(url: URL, requiredFileExtensions: [String]) throws -> [(fileBaseName: String, contents: Data)] {

        var results = [(fileBaseName: String, contents: Data)]()

        guard let zipFile = unzOpen64(url.path) else {
            throw ZipArchiveError.cantOpenInputZipFile
        }
        defer {
            unzClose(zipFile)
        }
        guard unzGoToFirstFile(zipFile) == UNZ_OK else { throw ZipArchiveError.badArchive }

        var resultOfGoToNextFile: Int32
        repeat {
            guard unzOpenCurrentFile(zipFile) == UNZ_OK else { throw ZipArchiveError.badArchive }

            let bufferSize = 16384 // 16 KiB
            var fileNameBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: bufferSize)
            var dataBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: bufferSize)

            defer {
                fileNameBuffer.deallocate()
                dataBuffer.deallocate()
            }

            guard unzGetCurrentFileInfo64(zipFile, nil, fileNameBuffer, UInt(bufferSize), nil, 0, nil, 0) == UNZ_OK else { throw ZipArchiveError.badArchive }

            let lastChar = String(cString: fileNameBuffer).suffix(1)
            let isDirectory = (lastChar == "/" || lastChar == "\\")
            let fileURL = URL(fileURLWithFileSystemRepresentation: fileNameBuffer, isDirectory: isDirectory, relativeTo: nil)

            if !isDirectory && requiredFileExtensions.contains(fileURL.pathExtension) {
                var unzippedData = Data()
                var bytesRead: Int32 = 0
                repeat {
                    bytesRead = unzReadCurrentFile(zipFile, dataBuffer, UInt32(bufferSize))
                    if bytesRead > 0 {
                        let dataRead = dataBuffer.withMemoryRebound(to: UInt8.self, capacity: bufferSize) {
                            return Data(bytes: $0, count: Int(bytesRead))
                        }
                        unzippedData.append(dataRead)
                    }
                } while bytesRead > 0
                results.append((fileBaseName: fileURL.deletingPathExtension().lastPathComponent, contents: unzippedData))
            }

            guard unzCloseCurrentFile(zipFile) == UNZ_OK else { throw ZipArchiveError.badArchive }

            resultOfGoToNextFile = unzGoToNextFile(zipFile)
        } while resultOfGoToNextFile == UNZ_OK

        if resultOfGoToNextFile == UNZ_END_OF_LIST_OF_FILE {
            return results
        } else {
            throw ZipArchiveError.badArchive
        }
    }
}