From 804585f13db1987883ae356d78443adc1f5f810e Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sat, 27 Oct 2018 10:40:13 +0200 Subject: [PATCH 1/7] Fix/improve some poor filename parsing --- .../ConnectionService+Configurations.swift | 2 +- .../Model/ConnectionService+Migration.swift | 4 ++-- .../Sources/Model/ConnectionService.swift | 7 +++---- .../Services/InfrastructureFactory.swift | 2 +- .../ConnectionServiceTests.swift | 19 +++++++++++++++++++ 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Passepartout/Sources/Model/ConnectionService+Configurations.swift b/Passepartout/Sources/Model/ConnectionService+Configurations.swift index c22f1dc8..9a717fc9 100644 --- a/Passepartout/Sources/Model/ConnectionService+Configurations.swift +++ b/Passepartout/Sources/Model/ConnectionService+Configurations.swift @@ -44,6 +44,6 @@ extension ConnectionService { private func targetConfigurationURL(for profile: ConnectionProfile) -> URL { let contextURL = ConnectionService.ProfileKey(profile).contextURL(in: self) - return contextURL.appendingPathComponent("\(profile.id).ovpn") + return contextURL.appendingPathComponent(profile.id).appendingPathExtension("ovpn") } } diff --git a/Passepartout/Sources/Model/ConnectionService+Migration.swift b/Passepartout/Sources/Model/ConnectionService+Migration.swift index 802a6f0b..d220f1b9 100644 --- a/Passepartout/Sources/Model/ConnectionService+Migration.swift +++ b/Passepartout/Sources/Model/ConnectionService+Migration.swift @@ -145,7 +145,7 @@ extension ConnectionService { // provider["id"] = id // provider.removeValue(forKey: "name") - let url = providersParentURL.appendingPathComponent("\(id).json") + let url = providersParentURL.appendingPathComponent(id).appendingPathExtension("json") let data = try JSONSerialization.data(withJSONObject: provider, options: []) try data.write(to: url) } else if var host = p["host"] as? [String: Any] { @@ -155,7 +155,7 @@ extension ConnectionService { // host["id"] = id // host.removeValue(forKey: "title") - let url = hostsParentURL.appendingPathComponent("\(id).json") + let url = hostsParentURL.appendingPathComponent(id).appendingPathExtension("json") let data = try JSONSerialization.data(withJSONObject: host, options: []) try data.write(to: url) } diff --git a/Passepartout/Sources/Model/ConnectionService.swift b/Passepartout/Sources/Model/ConnectionService.swift index cf04e423..954d50d7 100644 --- a/Passepartout/Sources/Model/ConnectionService.swift +++ b/Passepartout/Sources/Model/ConnectionService.swift @@ -304,15 +304,14 @@ class ConnectionService: Codable { } private static func profileId(fromURL url: URL) -> String? { - let filename = url.lastPathComponent - guard let extRange = filename.range(of: ".json") else { + guard url.pathExtension == "json" else { return nil } - return String(filename[filename.startIndex.. URL { - return directory.appendingPathComponent("\(profileId).json") + return directory.appendingPathComponent(profileId).appendingPathExtension("json") } // MARK: Profiles diff --git a/Passepartout/Sources/Services/InfrastructureFactory.swift b/Passepartout/Sources/Services/InfrastructureFactory.swift index cbcecdce..d8fbcb58 100644 --- a/Passepartout/Sources/Services/InfrastructureFactory.swift +++ b/Passepartout/Sources/Services/InfrastructureFactory.swift @@ -203,7 +203,7 @@ class InfrastructureFactory { } private func cacheURL(for name: Infrastructure.Name) -> URL { - return cachePath.appendingPathComponent("\(name.webName).json") + return cachePath.appendingPathComponent(name.webName).appendingPathExtension("json") } private func cacheModificationDate(for name: Infrastructure.Name) -> Date? { diff --git a/PassepartoutTests-iOS/ConnectionServiceTests.swift b/PassepartoutTests-iOS/ConnectionServiceTests.swift index 0f315da8..4a77003b 100644 --- a/PassepartoutTests-iOS/ConnectionServiceTests.swift +++ b/PassepartoutTests-iOS/ConnectionServiceTests.swift @@ -58,4 +58,23 @@ class ConnectionServiceTests: XCTestCase { XCTAssert(activeProfile.parameters.sessionConfiguration.cipher == .aes256cbc) XCTAssert(activeProfile.parameters.sessionConfiguration.ca.pem == "bogus+ca") } + + func testPathExtension() { + XCTAssertTrue(privateTestPathExtension("file:///foo/bar/johndoe.json")) + XCTAssertFalse(privateTestPathExtension("file:///foo/bar/break.json.johndoe.json")) + } + + private func privateTestPathExtension(_ string: String) -> Bool { + let url = URL(string: string)! + let filename = url.lastPathComponent + guard let extRange = filename.range(of: ".json") else { + return false + } + guard url.pathExtension == "json" else { + return false + } + let name1 = String(filename[filename.startIndex.. Date: Sat, 27 Oct 2018 10:55:50 +0200 Subject: [PATCH 2/7] Convert some warnings to errors --- .../Scenes/ConfigurationViewController.swift | 2 +- .../Sources/Model/ConnectionService+Migration.swift | 2 +- Passepartout/Sources/Model/ConnectionService.swift | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Passepartout-iOS/Scenes/ConfigurationViewController.swift b/Passepartout-iOS/Scenes/ConfigurationViewController.swift index 64db865f..34208abb 100644 --- a/Passepartout-iOS/Scenes/ConfigurationViewController.swift +++ b/Passepartout-iOS/Scenes/ConfigurationViewController.swift @@ -125,7 +125,7 @@ class ConfigurationViewController: UIViewController, TableModelHost { do { (_, originalConfiguration) = try TunnelKitProvider.Configuration.parsed(from: url) } catch let e { - log.warning("Could not parse original configuration: \(e)") + log.error("Could not parse original configuration: \(e)") return } initialConfiguration = originalConfiguration.sessionConfiguration diff --git a/Passepartout/Sources/Model/ConnectionService+Migration.swift b/Passepartout/Sources/Model/ConnectionService+Migration.swift index d220f1b9..4a9066ca 100644 --- a/Passepartout/Sources/Model/ConnectionService+Migration.swift +++ b/Passepartout/Sources/Model/ConnectionService+Migration.swift @@ -35,7 +35,7 @@ extension ConnectionService { // log.verbose(String(data: newData, encoding: .utf8)!) try newData.write(to: to) } catch let e { - log.warning("Could not migrate service: \(e)") + log.error("Could not migrate service: \(e)") } } diff --git a/Passepartout/Sources/Model/ConnectionService.swift b/Passepartout/Sources/Model/ConnectionService.swift index 954d50d7..b17e21a9 100644 --- a/Passepartout/Sources/Model/ConnectionService.swift +++ b/Passepartout/Sources/Model/ConnectionService.swift @@ -221,7 +221,7 @@ class ConnectionService: Codable { cache[key] = PlaceholderConnectionProfile(key) } } catch let e { - log.warning("Could not list provider contents: \(e) (\(providersURL))") + log.error("Could not list provider contents: \(e) (\(providersURL))") } do { let files = try fm.contentsOfDirectory(at: hostsURL, includingPropertiesForKeys: nil, options: []) @@ -234,7 +234,7 @@ class ConnectionService: Codable { cache[key] = PlaceholderConnectionProfile(key) } } catch let e { - log.warning("Could not list host contents: \(e) (\(hostsURL))") + log.error("Could not list host contents: \(e) (\(hostsURL))") } } @@ -257,7 +257,7 @@ class ConnectionService: Codable { try data.write(to: url) log.debug("Saved provider '\(profile.id)'") } catch let e { - log.warning("Could not save provider '\(profile.id)': \(e)") + log.error("Could not save provider '\(profile.id)': \(e)") continue } } else if let profile = entry as? HostConnectionProfile { @@ -267,7 +267,7 @@ class ConnectionService: Codable { try data.write(to: url) log.debug("Saved host '\(profile.id)'") } catch let e { - log.warning("Could not save host '\(profile.id)': \(e)") + log.error("Could not save host '\(profile.id)': \(e)") continue } } else if let placeholder = entry as? PlaceholderConnectionProfile { @@ -292,7 +292,7 @@ class ConnectionService: Codable { } cache[key] = profile } catch let e { - log.warning("Could not decode profile JSON: \(e)") + log.error("Could not decode profile JSON: \(e)") return nil } } From 0d14349bca46da21fc2f19ff357ad8a1549823eb Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sat, 27 Oct 2018 11:03:41 +0200 Subject: [PATCH 3/7] Move filename charset to extensions --- .../Organizer/WizardHostViewController.swift | 9 ++------ Passepartout/Sources/AppConstants.swift | 10 --------- Passepartout/Sources/Utils.swift | 21 +++++++++++++++++++ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift b/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift index 70be468c..04a5be1e 100644 --- a/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift @@ -33,11 +33,6 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard { private struct ParsedFile { let url: URL - var filename: String { - let raw = url.deletingPathExtension().lastPathComponent - return raw.components(separatedBy: AppConstants.Store.filenameCharset.inverted).joined(separator: "_") - } - let hostname: String let configuration: TunnelKitProvider.Configuration @@ -118,7 +113,7 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard { return } if field.text?.isEmpty ?? true { - field.text = parsedFile?.filename + field.text = parsedFile?.url.normalizedFilename } } @@ -216,7 +211,7 @@ extension WizardHostViewController { let cell = Cells.field.dequeue(from: tableView, for: indexPath) cell.caption = L10n.Wizards.Host.Cells.TitleInput.caption cell.captionWidth = 100.0 - cell.allowedCharset = AppConstants.Store.filenameCharset + cell.allowedCharset = .filename cell.field.placeholder = L10n.Wizards.Host.Cells.TitleInput.placeholder cell.field.clearButtonMode = .always cell.field.returnKeyType = .done diff --git a/Passepartout/Sources/AppConstants.swift b/Passepartout/Sources/AppConstants.swift index ed6f908a..f856014b 100644 --- a/Passepartout/Sources/AppConstants.swift +++ b/Passepartout/Sources/AppConstants.swift @@ -40,16 +40,6 @@ class AppConstants { static let providersDirectory = "Providers" static let hostsDirectory = "Hosts" - - static let filenameCharset: CharacterSet = { - var chars: CharacterSet = .decimalDigits - let english = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - let symbols = "-_" - chars.formUnion(CharacterSet(charactersIn: english)) - chars.formUnion(CharacterSet(charactersIn: english.lowercased())) - chars.formUnion(CharacterSet(charactersIn: symbols)) - return chars - }() } class VPN { diff --git a/Passepartout/Sources/Utils.swift b/Passepartout/Sources/Utils.swift index 0c178870..d90a7e51 100644 --- a/Passepartout/Sources/Utils.swift +++ b/Passepartout/Sources/Utils.swift @@ -201,3 +201,24 @@ extension String { } } } + +extension CharacterSet { + static let filename: CharacterSet = { + var chars: CharacterSet = .decimalDigits + let english = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + let symbols = "-_" + chars.formUnion(CharacterSet(charactersIn: english)) + chars.formUnion(CharacterSet(charactersIn: english.lowercased())) + chars.formUnion(CharacterSet(charactersIn: symbols)) + return chars + }() +} + +extension URL { + private static let illegalCharacterFallback = "_" + + var normalizedFilename: String { + let filename = deletingPathExtension().lastPathComponent + return filename.components(separatedBy: CharacterSet.filename.inverted).joined(separator: URL.illegalCharacterFallback) + } +} From 422c4da09cbef996be4cd19c53e123c162181e8a Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sat, 27 Oct 2018 11:36:41 +0200 Subject: [PATCH 4/7] Move ParsedFile out of WizardHostVC --- Passepartout-iOS/Global/IssueReporter.swift | 5 ++--- .../Scenes/ConfigurationViewController.swift | 8 ++++---- .../Organizer/WizardHostViewController.swift | 13 +------------ .../TunnelKitProvider+FileConfiguration.swift | 17 ++++++++++++++--- .../FileConfigurationTests.swift | 9 ++++----- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/Passepartout-iOS/Global/IssueReporter.swift b/Passepartout-iOS/Global/IssueReporter.swift index 473e8a5b..80ccd093 100644 --- a/Passepartout-iOS/Global/IssueReporter.swift +++ b/Passepartout-iOS/Global/IssueReporter.swift @@ -90,10 +90,9 @@ class IssueReporter: NSObject { vc.addAttachmentData(attachment, mimeType: AppConstants.IssueReporter.MIME.debugLog, fileName: AppConstants.IssueReporter.Filenames.debugLog) } if let url = configurationURL { - var lines: [String] = [] do { - _ = try TunnelKitProvider.Configuration.parsed(from: url, stripped: &lines) - if let attachment = lines.joined(separator: "\n").data(using: .utf8) { + let parsedFile = try TunnelKitProvider.Configuration.parsed(from: url, returnsStripped: true) + if let attachment = parsedFile.strippedLines?.joined(separator: "\n").data(using: .utf8) { vc.addAttachmentData(attachment, mimeType: AppConstants.IssueReporter.MIME.configuration, fileName: AppConstants.IssueReporter.Filenames.configuration) } } catch { diff --git a/Passepartout-iOS/Scenes/ConfigurationViewController.swift b/Passepartout-iOS/Scenes/ConfigurationViewController.swift index 34208abb..b72253f3 100644 --- a/Passepartout-iOS/Scenes/ConfigurationViewController.swift +++ b/Passepartout-iOS/Scenes/ConfigurationViewController.swift @@ -117,18 +117,18 @@ class ConfigurationViewController: UIViewController, TableModelHost { // MARK: Actions private func resetOriginalConfiguration() { - guard let url = originalConfigurationURL else { + guard let originalURL = originalConfigurationURL else { log.warning("Resetting with no original configuration set? Bad table model?") return } - let originalConfiguration: TunnelKitProvider.Configuration + let parsedFile: ParsedFile do { - (_, originalConfiguration) = try TunnelKitProvider.Configuration.parsed(from: url) + parsedFile = try TunnelKitProvider.Configuration.parsed(from: originalURL) } catch let e { log.error("Could not parse original configuration: \(e)") return } - initialConfiguration = originalConfiguration.sessionConfiguration + initialConfiguration = parsedFile.configuration.sessionConfiguration configuration = initialConfiguration.builder() itemRefresh.isEnabled = true // allow for manual reconnection tableView.reloadData() diff --git a/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift b/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift index 04a5be1e..c94168de 100644 --- a/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift @@ -30,14 +30,6 @@ import SwiftyBeaver private let log = SwiftyBeaver.self class WizardHostViewController: UITableViewController, TableModelHost, Wizard { - private struct ParsedFile { - let url: URL - - let hostname: String - - let configuration: TunnelKitProvider.Configuration - } - @IBOutlet private weak var itemNext: UIBarButtonItem! private let existingHosts: [String] = { @@ -97,15 +89,12 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard { func setConfigurationURL(_ url: URL) throws { log.debug("Parsing configuration URL: \(url)") - let hostname: String - let configuration: TunnelKitProvider.Configuration do { - (hostname, configuration) = try TunnelKitProvider.Configuration.parsed(from: url) + parsedFile = try TunnelKitProvider.Configuration.parsed(from: url) } catch let e { log.error("Could not parse .ovpn configuration file: \(e)") throw e } - parsedFile = ParsedFile(url: url, hostname: hostname, configuration: configuration) } private func useSuggestedTitle() { diff --git a/Passepartout/Sources/VPN/TunnelKitProvider+FileConfiguration.swift b/Passepartout/Sources/VPN/TunnelKitProvider+FileConfiguration.swift index 7c70d081..8c34aace 100644 --- a/Passepartout/Sources/VPN/TunnelKitProvider+FileConfiguration.swift +++ b/Passepartout/Sources/VPN/TunnelKitProvider+FileConfiguration.swift @@ -29,6 +29,16 @@ import SwiftyBeaver private let log = SwiftyBeaver.self +struct ParsedFile { + let url: URL + + let hostname: String + + let configuration: TunnelKitProvider.Configuration + + let strippedLines: [String]? +} + extension TunnelKitProvider.Configuration { private struct Regex { static let proto = Utils.regex("^proto +(udp6?|tcp6?)") @@ -62,8 +72,9 @@ extension TunnelKitProvider.Configuration { static let blockEnd = Utils.regex("^<\\/[\\w\\-]+>") } - static func parsed(from url: URL, stripped: UnsafeMutablePointer<[String]>? = nil) throws -> (String, TunnelKitProvider.Configuration) { + static func parsed(from url: URL, returnsStripped: Bool = false) throws -> ParsedFile { let lines = try String(contentsOf: url).trimmedLines() + var strippedLines: [String]? = returnsStripped ? [] : nil var defaultProto: TunnelKitProvider.SocketType? var defaultPort: UInt16? @@ -94,7 +105,7 @@ extension TunnelKitProvider.Configuration { var strippedLine = line defer { if isHandled { - stripped?.pointee.append(strippedLine) + strippedLines?.append(strippedLine) } } @@ -311,7 +322,7 @@ extension TunnelKitProvider.Configuration { var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build()) builder.endpointProtocols = endpointProtocols - return (hostname, builder.build()) + return ParsedFile(url: url, hostname: hostname, configuration: builder.build(), strippedLines: strippedLines) } } diff --git a/PassepartoutTests-iOS/FileConfigurationTests.swift b/PassepartoutTests-iOS/FileConfigurationTests.swift index ead8d5b5..acd967dc 100644 --- a/PassepartoutTests-iOS/FileConfigurationTests.swift +++ b/PassepartoutTests-iOS/FileConfigurationTests.swift @@ -39,16 +39,15 @@ class FileConfigurationTests: XCTestCase { } func testPIA() throws { - let cfg = try TunnelKitProvider.Configuration.parsed(from: url(withName: "pia-hungary")).1 + let cfg = try TunnelKitProvider.Configuration.parsed(from: url(withName: "pia-hungary")).configuration XCTAssertEqual(cfg.sessionConfiguration.cipher, .aes128cbc) XCTAssertEqual(cfg.sessionConfiguration.digest, .sha1) } func testStripped() throws { - var lines: [String] = [] - _ = try TunnelKitProvider.Configuration.parsed(from: url(withName: "pia-hungary"), stripped: &lines) - let cfg = lines.joined(separator: "\n") - print(cfg) + let lines = try TunnelKitProvider.Configuration.parsed(from: url(withName: "pia-hungary"), returnsStripped: true).strippedLines! + let stripped = lines.joined(separator: "\n") + print(stripped) } private func url(withName name: String) -> URL { From 8c1e6d00ca4b66733e3d5c9fe649fb7748bafab1 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sat, 27 Oct 2018 11:51:50 +0200 Subject: [PATCH 5/7] Parse .ovpn file outside of wizard context --- Passepartout-iOS/AppDelegate.swift | 76 +++++++------------ .../Global/ParsedFile+Alerts.swift | 64 ++++++++++++++++ .../Organizer/WizardHostViewController.swift | 13 +--- Passepartout.xcodeproj/project.pbxproj | 4 + .../Resources/en.lproj/Localizable.strings | 9 ++- Passepartout/Sources/SwiftGen+Strings.swift | 58 +++++++------- 6 files changed, 133 insertions(+), 91 deletions(-) create mode 100644 Passepartout-iOS/Global/ParsedFile+Alerts.swift diff --git a/Passepartout-iOS/AppDelegate.swift b/Passepartout-iOS/AppDelegate.swift index f8ddbce8..795ea6b4 100644 --- a/Passepartout-iOS/AppDelegate.swift +++ b/Passepartout-iOS/AppDelegate.swift @@ -24,9 +24,6 @@ // import UIKit -import SwiftyBeaver - -private let log = SwiftyBeaver.self @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { @@ -87,60 +84,41 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele } // MARK: URLs - + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { guard let root = window?.rootViewController else { fatalError("No window.rootViewController?") } - do { - - // already presented: update URL - if let nav = root.presentedViewController as? UINavigationController, let wizard = nav.topViewController as? WizardHostViewController { - try wizard.setConfigurationURL(url) - return true - } - - // present now - let nav = StoryboardScene.Organizer.wizardHostIdentifier.instantiate() - guard let wizard = nav.topViewController as? WizardHostViewController else { - fatalError("Expected WizardHostViewController from storyboard") - } - try wizard.setConfigurationURL(url) - - // best effort to delegate to main vc - let split = root as? UISplitViewController - let master = split?.viewControllers.first as? UINavigationController - master?.viewControllers.forEach { - if let organizerVC = $0 as? OrganizerViewController { - wizard.delegate = organizerVC - } - } - nav.modalPresentationStyle = .formSheet - root.present(nav, animated: true, completion: nil) - } catch ApplicationError.missingConfiguration(let option) { - let message = L10n.Wizards.Host.Alerts.Missing.message(option) - alertConfigurationImportError(url: url, in: root, withMessage: message) - } catch ApplicationError.unsupportedConfiguration(let option) { - let message = L10n.Wizards.Host.Alerts.Unsupported.message(option) - alertConfigurationImportError(url: url, in: root, withMessage: message) - } catch let e { - let message = L10n.Wizards.Host.Alerts.Parsing.message(e.localizedDescription) - alertConfigurationImportError(url: url, in: root, withMessage: message) + guard let parsedFile = ParsedFile.from(url, withErrorAlertIn: root) else { + return true } + + // already presented: update parsed configuration + if let nav = root.presentedViewController as? UINavigationController, let wizard = nav.topViewController as? WizardHostViewController { + wizard.parsedFile = parsedFile + return true + } + + // present now + let nav = StoryboardScene.Organizer.wizardHostIdentifier.instantiate() + guard let wizard = nav.topViewController as? WizardHostViewController else { + fatalError("Expected WizardHostViewController from storyboard") + } + wizard.parsedFile = parsedFile + + // best effort to delegate to main vc + let split = root as? UISplitViewController + let master = split?.viewControllers.first as? UINavigationController + master?.viewControllers.forEach { + if let organizerVC = $0 as? OrganizerViewController { + wizard.delegate = organizerVC + } + } + nav.modalPresentationStyle = .formSheet + root.present(nav, animated: true, completion: nil) return true } - - private func alertConfigurationImportError(url: URL, in vc: UIViewController, withMessage message: String) { - let alert = Macros.alert(L10n.Organizer.Sections.Hosts.header, message) -// alert.addDefaultAction(L10n.Wizards.Host.Alerts.Buttons.report) { -// var attach = IssueReporter.Attachments(debugLog: false, configurationURL: url) -// attach.description = message -// IssueReporter.shared.present(in: vc, withAttachments: attach) -// } - alert.addCancelAction(L10n.Global.cancel) - vc.present(alert, animated: true, completion: nil) - } } extension UISplitViewController { diff --git a/Passepartout-iOS/Global/ParsedFile+Alerts.swift b/Passepartout-iOS/Global/ParsedFile+Alerts.swift new file mode 100644 index 00000000..7cc370dd --- /dev/null +++ b/Passepartout-iOS/Global/ParsedFile+Alerts.swift @@ -0,0 +1,64 @@ +// +// ParsedFile+Alerts.swift +// Passepartout-iOS +// +// Created by Davide De Rosa on 10/27/18. +// Copyright (c) 2018 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import UIKit +import TunnelKit +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +extension ParsedFile { + static func from(_ url: URL, withErrorAlertIn viewController: UIViewController) -> ParsedFile? { + log.debug("Parsing configuration URL: \(url)") + do { + return try TunnelKitProvider.Configuration.parsed(from: url) + } catch ApplicationError.missingConfiguration(let option) { + log.error("Could not parse configuration URL: missing configuration, \(option)") + let message = L10n.ParsedFile.Alerts.Missing.message(option) + alertConfigurationImportError(url: url, in: viewController, withMessage: message) + } catch ApplicationError.unsupportedConfiguration(let option) { + log.error("Could not parse configuration URL: unsupported configuration, \(option)") + let message = L10n.ParsedFile.Alerts.Unsupported.message(option) + alertConfigurationImportError(url: url, in: viewController, withMessage: message) + } catch let e { + log.error("Could not parse configuration URL: \(e)") + let message = L10n.ParsedFile.Alerts.Parsing.message(e.localizedDescription) + alertConfigurationImportError(url: url, in: viewController, withMessage: message) + } + return nil + } + + private static func alertConfigurationImportError(url: URL, in vc: UIViewController, withMessage message: String) { + let alert = Macros.alert(url.normalizedFilename, message) +// alert.addDefaultAction(L10n.ParsedFile.Alerts.Buttons.report) { +// var attach = IssueReporter.Attachments(debugLog: false, configurationURL: url) +// attach.description = message +// IssueReporter.shared.present(in: vc, withAttachments: attach) +// } + alert.addCancelAction(L10n.Global.ok) + vc.present(alert, animated: true, completion: nil) + } +} diff --git a/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift b/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift index c94168de..db2c4b73 100644 --- a/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift @@ -36,7 +36,7 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard { return TransientStore.shared.service.ids(forContext: .host).sorted() }() - private var parsedFile: ParsedFile? { + var parsedFile: ParsedFile? { didSet { useSuggestedTitle() } @@ -86,17 +86,6 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard { // MARK: Actions - func setConfigurationURL(_ url: URL) throws { - log.debug("Parsing configuration URL: \(url)") - - do { - parsedFile = try TunnelKitProvider.Configuration.parsed(from: url) - } catch let e { - log.error("Could not parse .ovpn configuration file: \(e)") - throw e - } - } - private func useSuggestedTitle() { guard let field = cellTitle?.field else { return diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index 7ae60691..eafffd55 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 0E89DFD0213F223400741BA1 /* Wizard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E89DFCF213F223400741BA1 /* Wizard.swift */; }; 0E8D97E221388B52006FB4A0 /* InfrastructurePreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8D97E121388B52006FB4A0 /* InfrastructurePreset.swift */; }; 0E8D97E521389277006FB4A0 /* pia.json in Resources */ = {isa = PBXBuildFile; fileRef = 0E8D97E421389276006FB4A0 /* pia.json */; }; + 0EA068F4218475F800C320AD /* ParsedFile+Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA068F3218475F800C320AD /* ParsedFile+Alerts.swift */; }; 0EAAD71920E6669A0088754A /* GroupConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE8DED20C93E4C004C739C /* GroupConstants.swift */; }; 0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */; }; 0EBBE8F221822B4D00106008 /* ConnectionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBE8F021822B4D00106008 /* ConnectionServiceTests.swift */; }; @@ -166,6 +167,7 @@ 0E89DFCF213F223400741BA1 /* Wizard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wizard.swift; sourceTree = ""; }; 0E8D97E121388B52006FB4A0 /* InfrastructurePreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfrastructurePreset.swift; sourceTree = ""; }; 0E8D97E421389276006FB4A0 /* pia.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = pia.json; sourceTree = ""; }; + 0EA068F3218475F800C320AD /* ParsedFile+Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParsedFile+Alerts.swift"; sourceTree = ""; }; 0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Search.swift"; sourceTree = ""; }; 0EBBE8F021822B4D00106008 /* ConnectionServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionServiceTests.swift; sourceTree = ""; }; 0EBBE8F121822B4D00106008 /* ConnectionService.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ConnectionService.json; sourceTree = ""; }; @@ -435,6 +437,7 @@ 0EFD943D215BE10800529B64 /* IssueReporter.swift */, 0E4FD7F020D58618002221FF /* Macros.swift */, 0ED38AE9214054A50004D387 /* OptionViewController.swift */, + 0EA068F3218475F800C320AD /* ParsedFile+Alerts.swift */, 0EDE8DE320C89028004C739C /* SwiftGen+Storyboards.swift */, 0E05C61C20D27C82006EE732 /* Theme.swift */, 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */, @@ -852,6 +855,7 @@ 0E57F63E20C83FC5008323CF /* ServiceViewController.swift in Sources */, 0E39BCF0214B9EF10035E9DE /* WebServices.swift in Sources */, 0EDE8DE720C93945004C739C /* Credentials.swift in Sources */, + 0EA068F4218475F800C320AD /* ParsedFile+Alerts.swift in Sources */, 0ED38AF2214177920004D387 /* VPNProvider.swift in Sources */, 0E4C9CB920DB9BC600A0C59C /* TrustedNetworks.swift in Sources */, 0E57F63C20C83FC5008323CF /* AppDelegate.swift in Sources */, diff --git a/Passepartout/Resources/en.lproj/Localizable.strings b/Passepartout/Resources/en.lproj/Localizable.strings index aec45ff9..67084496 100644 --- a/Passepartout/Resources/en.lproj/Localizable.strings +++ b/Passepartout/Resources/en.lproj/Localizable.strings @@ -52,10 +52,11 @@ "wizards.host.cells.title_input.placeholder" = "My Profile"; "wizards.host.sections.existing.header" = "Existing profiles"; "wizards.host.alerts.existing.message" = "A host profile with the same title already exists. Replace it?"; -"wizards.host.alerts.missing.message" = "The configuration file lacks a required option (%@)."; -"wizards.host.alerts.unsupported.message" = "The configuration file contains an unsupported option (%@)."; -"wizards.host.alerts.parsing.message" = "Unable to parse the provided configuration file (%@)."; -"wizards.host.alerts.buttons.report" = "Report an issue"; + +"parsed_file.alerts.missing.message" = "The configuration file lacks a required option (%@)."; +"parsed_file.alerts.unsupported.message" = "The configuration file contains an unsupported option (%@)."; +"parsed_file.alerts.parsing.message" = "Unable to parse the provided configuration file (%@)."; +"parsed_file.alerts.buttons.report" = "Report an issue"; "service.welcome.message" = "Welcome to Passepartout!\n\nUse the organizer to add a new profile."; "service.sections.general.header" = "General"; diff --git a/Passepartout/Sources/SwiftGen+Strings.swift b/Passepartout/Sources/SwiftGen+Strings.swift index e6827a50..29293221 100644 --- a/Passepartout/Sources/SwiftGen+Strings.swift +++ b/Passepartout/Sources/SwiftGen+Strings.swift @@ -404,6 +404,38 @@ internal enum L10n { } } + internal enum ParsedFile { + + internal enum Alerts { + + internal enum Buttons { + /// Report an issue + internal static let report = L10n.tr("Localizable", "parsed_file.alerts.buttons.report") + } + + internal enum Missing { + /// The configuration file lacks a required option (%@). + internal static func message(_ p1: String) -> String { + return L10n.tr("Localizable", "parsed_file.alerts.missing.message", p1) + } + } + + internal enum Parsing { + /// Unable to parse the provided configuration file (%@). + internal static func message(_ p1: String) -> String { + return L10n.tr("Localizable", "parsed_file.alerts.parsing.message", p1) + } + } + + internal enum Unsupported { + /// The configuration file contains an unsupported option (%@). + internal static func message(_ p1: String) -> String { + return L10n.tr("Localizable", "parsed_file.alerts.unsupported.message", p1) + } + } + } + } + internal enum Provider { internal enum Preset { @@ -734,36 +766,10 @@ internal enum L10n { internal enum Alerts { - internal enum Buttons { - /// Report an issue - internal static let report = L10n.tr("Localizable", "wizards.host.alerts.buttons.report") - } - internal enum Existing { /// A host profile with the same title already exists. Replace it? internal static let message = L10n.tr("Localizable", "wizards.host.alerts.existing.message") } - - internal enum Missing { - /// The configuration file lacks a required option (%@). - internal static func message(_ p1: String) -> String { - return L10n.tr("Localizable", "wizards.host.alerts.missing.message", p1) - } - } - - internal enum Parsing { - /// Unable to parse the provided configuration file (%@). - internal static func message(_ p1: String) -> String { - return L10n.tr("Localizable", "wizards.host.alerts.parsing.message", p1) - } - } - - internal enum Unsupported { - /// The configuration file contains an unsupported option (%@). - internal static func message(_ p1: String) -> String { - return L10n.tr("Localizable", "wizards.host.alerts.unsupported.message", p1) - } - } } internal enum Cells { From 9e933d68c50e933e5d2af8987d0f13546ca896d5 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sat, 27 Oct 2018 12:07:39 +0200 Subject: [PATCH 6/7] Improve unsupported options filter --- .../VPN/TunnelKitProvider+FileConfiguration.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Passepartout/Sources/VPN/TunnelKitProvider+FileConfiguration.swift b/Passepartout/Sources/VPN/TunnelKitProvider+FileConfiguration.swift index 8c34aace..75899eec 100644 --- a/Passepartout/Sources/VPN/TunnelKitProvider+FileConfiguration.swift +++ b/Passepartout/Sources/VPN/TunnelKitProvider+FileConfiguration.swift @@ -59,17 +59,20 @@ extension TunnelKitProvider.Configuration { static let renegSec = Utils.regex("^reneg-sec +\\d+") - static let fragment = Utils.regex("^fragment +\\d+") - - static let proxy = Utils.regex("^\\w+-proxy") - static let keyDirection = Utils.regex("^key-direction +\\d") - static let externalFiles = Utils.regex("^(ca|cert|key|tls-auth|tls-crypt) ") - static let blockBegin = Utils.regex("^<[\\w\\-]+>") static let blockEnd = Utils.regex("^<\\/[\\w\\-]+>") + + // unsupported + +// static let fragment = Utils.regex("^fragment +\\d+") + static let fragment = Utils.regex("^fragment") + + static let proxy = Utils.regex("^\\w+-proxy") + + static let externalFiles = Utils.regex("^(ca|cert|key|tls-auth|tls-crypt) ") } static func parsed(from url: URL, returnsStripped: Bool = false) throws -> ParsedFile { From 013eaaabd4761a0bab9813964406b11b3da6dda9 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sat, 27 Oct 2018 12:18:47 +0200 Subject: [PATCH 7/7] Don't touch refresh if can communicate w/ original --- Passepartout-iOS/Scenes/ConfigurationViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Passepartout-iOS/Scenes/ConfigurationViewController.swift b/Passepartout-iOS/Scenes/ConfigurationViewController.swift index b72253f3..29f221a1 100644 --- a/Passepartout-iOS/Scenes/ConfigurationViewController.swift +++ b/Passepartout-iOS/Scenes/ConfigurationViewController.swift @@ -128,9 +128,9 @@ class ConfigurationViewController: UIViewController, TableModelHost { log.error("Could not parse original configuration: \(e)") return } + configuration = parsedFile.configuration.sessionConfiguration.builder() + itemRefresh.isEnabled = !configuration.canCommunicate(with: initialConfiguration) initialConfiguration = parsedFile.configuration.sessionConfiguration - configuration = initialConfiguration.builder() - itemRefresh.isEnabled = true // allow for manual reconnection tableView.reloadData() delegate?.configuration(didUpdate: initialConfiguration)