diff --git a/Podfile b/Podfile index e71e80b..980dd9f 100644 --- a/Podfile +++ b/Podfile @@ -9,6 +9,7 @@ target 'WireGuard' do pod 'PromiseKit/CorePromise' pod 'Disk' pod 'BNRCoreDataStack' + pod 'ZIPFoundation' post_install do | installer | require 'fileutils' diff --git a/Podfile.lock b/Podfile.lock index 03abb80..35bea46 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -3,12 +3,14 @@ PODS: - Disk (0.3.3) - PromiseKit/CorePromise (6.3.4) - SwiftLint (0.27.0) + - ZIPFoundation (0.9.6) DEPENDENCIES: - BNRCoreDataStack - Disk - PromiseKit/CorePromise - SwiftLint + - ZIPFoundation SPEC REPOS: https://github.com/cocoapods/specs.git: @@ -16,13 +18,15 @@ SPEC REPOS: - Disk - PromiseKit - SwiftLint + - ZIPFoundation SPEC CHECKSUMS: BNRCoreDataStack: d9d7d0ed1afd27dca5a903dde1250aa4c6dbc3f5 Disk: d1f55cd61f6ca20f368232d0c6e37e3c3dfcb63e PromiseKit: e1425568123ec844a944c93f2bcb29d511d39cf5 SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073 + ZIPFoundation: 68c35eb2637c21abe56dc60b3277636443bc1501 -PODFILE CHECKSUM: 82a0eed9b58c48f9be6c02c4e9d17a979a496c18 +PODFILE CHECKSUM: 5f7ab91f3becde6fa3b3b8842dc93ae44e92c61f COCOAPODS: 1.5.3 diff --git a/Resources/Settings.bundle/Acknowledgements.plist b/Resources/Settings.bundle/Acknowledgements.plist index 15b64b5..17a5bc9 100644 --- a/Resources/Settings.bundle/Acknowledgements.plist +++ b/Resources/Settings.bundle/Acknowledgements.plist @@ -134,6 +134,37 @@ SOFTWARE. Type PSGroupSpecifier + + FooterText + MIT License + +Copyright (c) 2017 Thomas Zoechling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + License + MIT + Title + ZIPFoundation + Type + PSGroupSpecifier + FooterText Generated by CocoaPods - https://cocoapods.org diff --git a/WireGuard.xcodeproj/project.pbxproj b/WireGuard.xcodeproj/project.pbxproj index fead2ea..38c5cfd 100644 --- a/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard.xcodeproj/project.pbxproj @@ -538,12 +538,14 @@ "${BUILT_PRODUCTS_DIR}/BNRCoreDataStack/BNRCoreDataStack.framework", "${BUILT_PRODUCTS_DIR}/Disk/Disk.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit/PromiseKit.framework", + "${BUILT_PRODUCTS_DIR}/ZIPFoundation/ZIPFoundation.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BNRCoreDataStack.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Disk.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZIPFoundation.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/WireGuard/Base.lproj/Main.storyboard b/WireGuard/Base.lproj/Main.storyboard index ccf19f2..e36f076 100644 --- a/WireGuard/Base.lproj/Main.storyboard +++ b/WireGuard/Base.lproj/Main.storyboard @@ -87,11 +87,18 @@ - - - - - + + + + + + + + + + + + diff --git a/WireGuard/Coordinators/AppCoordinator.swift b/WireGuard/Coordinators/AppCoordinator.swift index 5bb83e2..31de8f1 100644 --- a/WireGuard/Coordinators/AppCoordinator.swift +++ b/WireGuard/Coordinators/AppCoordinator.swift @@ -9,6 +9,7 @@ import Foundation import NetworkExtension import os.log +import ZIPFoundation import CoreData import BNRCoreDataStack @@ -111,6 +112,72 @@ class AppCoordinator: RootViewCoordinator { } + // swiftlint:disable next function_body_length + func exportConfigs(barButtonItem: UIBarButtonItem) { + guard let path = FileManager.default + .urls(for: .documentDirectory, in: .userDomainMask).first else { + return + } + let saveFileURL = path.appendingPathComponent("wireguard-export.zip") + do { + try FileManager.default.removeItem(at: saveFileURL) + } catch { + os_log("Failed to delete file: %{public}@ : %{public}@", log: Log.general, type: .error, saveFileURL.absoluteString, error.localizedDescription) + } + + guard let archive = Archive(url: saveFileURL, accessMode: .create) else { + return + } + + do { + var tunnelsByTitle = [String: [Tunnel]]() + let tunnels = try Tunnel.allInContext(persistentContainer.viewContext) + tunnels.forEach { + guard let title = $0.title ?? $0.tunnelIdentifier else { + // there is always a tunnelidentifier. + return + } + if let tunnels = tunnelsByTitle[title] { + tunnelsByTitle[title] = tunnels + [$0] + } else { + tunnelsByTitle[title] = [$0] + } + } + + func addEntry(title: String, tunnel: Tunnel) throws { + let data = tunnel.export().data(using: .utf8)! + let byteCount: UInt32 = UInt32(data.count) + try archive.addEntry(with: "\(title).conf", type: .file, uncompressedSize: byteCount, provider: { (position, size) -> Data in + return data.subdata(in: position ..< size) + }) + } + + try tunnelsByTitle.keys.forEach { + if let tunnels = tunnelsByTitle[$0] { + if tunnels.count == 1 { + try addEntry(title: $0, tunnel: tunnels[0]) + } else { + for (index, tunnel) in tunnels.enumerated() { + try addEntry(title: $0 + "-\(index + 1)", tunnel: tunnel) + } + } + } + } + } catch { + os_log("Failed to create archive file: %{public}@ : %{public}@", log: Log.general, type: .error, saveFileURL.absoluteString, error.localizedDescription) + return + } + + let activityViewController = UIActivityViewController( + activityItems: [saveFileURL], + applicationActivities: nil) + if let popoverPresentationController = activityViewController.popoverPresentationController { + popoverPresentationController.barButtonItem = barButtonItem + } + navigationController.present(activityViewController, animated: true) { + } + } + func exportConfig(tunnel: Tunnel, barButtonItem: UIBarButtonItem) { let exportString = tunnel.export() @@ -195,6 +262,10 @@ class AppCoordinator: RootViewCoordinator { } extension AppCoordinator: TunnelsTableViewControllerDelegate { + func exportTunnels(tunnelsTableViewController: TunnelsTableViewController, barButtonItem: UIBarButtonItem) { + self.exportConfigs(barButtonItem: barButtonItem) + } + func status(for tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) -> NEVPNStatus { let session = self.providerManager(for: tunnel)?.connection as? NETunnelProviderSession return session?.status ?? .invalid diff --git a/WireGuard/ViewControllers/TunnelsTableViewController.swift b/WireGuard/ViewControllers/TunnelsTableViewController.swift index 8198a2f..a9ab09b 100644 --- a/WireGuard/ViewControllers/TunnelsTableViewController.swift +++ b/WireGuard/ViewControllers/TunnelsTableViewController.swift @@ -13,6 +13,7 @@ import BNRCoreDataStack import NetworkExtension protocol TunnelsTableViewControllerDelegate: class { + func exportTunnels(tunnelsTableViewController: TunnelsTableViewController, barButtonItem: UIBarButtonItem) func addProvider(tunnelsTableViewController: TunnelsTableViewController) func connect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) func disconnect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) @@ -63,7 +64,11 @@ class TunnelsTableViewController: UITableViewController { tableView.tableFooterView = UIView(frame: CGRect.zero) } - @IBAction func addProvider(_ sender: Any) { + @IBAction func exportTunnels(_ sender: UIBarButtonItem) { + delegate?.exportTunnels(tunnelsTableViewController: self, barButtonItem: sender) + } + + @IBAction func addProvider(_ sender: UIBarButtonItem) { delegate?.addProvider(tunnelsTableViewController: self) }