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)
}