Merge pull request #13 from keeshux/attach-ovpn-to-report
Attach .ovpn to connectivity issue report
This commit is contained in:
commit
02c8e7b6ea
|
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Attach .ovpn when reporting a connectivity issue, stripped of sensitive data. [#13](https://github.com/keeshux/passepartout-ios/pull/13)
|
||||||
|
|
||||||
## 1.0 beta 1107 (2018-10-26)
|
## 1.0 beta 1107 (2018-10-26)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -118,17 +118,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
|
||||||
}
|
}
|
||||||
nav.modalPresentationStyle = .formSheet
|
nav.modalPresentationStyle = .formSheet
|
||||||
root.present(nav, animated: true, completion: nil)
|
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) {
|
} catch ApplicationError.unsupportedConfiguration(let option) {
|
||||||
let alert = Macros.alert(L10n.Organizer.Sections.Hosts.header, L10n.Wizards.Host.Alerts.unsupported(option))
|
let message = L10n.Wizards.Host.Alerts.Unsupported.message(option)
|
||||||
alert.addCancelAction(L10n.Global.ok)
|
alertConfigurationImportError(url: url, in: root, withMessage: message)
|
||||||
root.present(alert, animated: true, completion: nil)
|
|
||||||
} catch let e {
|
} catch let e {
|
||||||
let alert = Macros.alert(L10n.Organizer.Sections.Hosts.header, L10n.Wizards.Host.Alerts.parsing(e.localizedDescription))
|
let message = L10n.Wizards.Host.Alerts.Parsing.message(e.localizedDescription)
|
||||||
alert.addCancelAction(L10n.Global.ok)
|
alertConfigurationImportError(url: url, in: root, withMessage: message)
|
||||||
root.present(alert, animated: true, completion: nil)
|
|
||||||
}
|
}
|
||||||
return true
|
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 {
|
extension UISplitViewController {
|
||||||
|
|
|
@ -24,9 +24,28 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import TunnelKit
|
||||||
import MessageUI
|
import MessageUI
|
||||||
|
|
||||||
class IssueReporter: NSObject {
|
class IssueReporter: NSObject {
|
||||||
|
struct Attachments {
|
||||||
|
let debugLog: Bool
|
||||||
|
|
||||||
|
let configurationURL: URL?
|
||||||
|
|
||||||
|
var description: String?
|
||||||
|
|
||||||
|
init(debugLog: Bool, configurationURL: URL?) {
|
||||||
|
self.debugLog = debugLog
|
||||||
|
self.configurationURL = configurationURL
|
||||||
|
}
|
||||||
|
|
||||||
|
init(debugLog: Bool, profile: ConnectionProfile) {
|
||||||
|
let url = TransientStore.shared.service.configurationURL(for: profile)
|
||||||
|
self.init(debugLog: debugLog, configurationURL: url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static let shared = IssueReporter()
|
static let shared = IssueReporter()
|
||||||
|
|
||||||
private weak var viewController: UIViewController?
|
private weak var viewController: UIViewController?
|
||||||
|
@ -35,7 +54,7 @@ class IssueReporter: NSObject {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
func present(in viewController: UIViewController) {
|
func present(in viewController: UIViewController, withAttachments attachments: Attachments) {
|
||||||
guard MFMailComposeViewController.canSendMail() else {
|
guard MFMailComposeViewController.canSendMail() else {
|
||||||
let alert = Macros.alert(L10n.IssueReporter.title, L10n.IssueReporter.Alerts.EmailNotConfigured.message)
|
let alert = Macros.alert(L10n.IssueReporter.title, L10n.IssueReporter.Alerts.EmailNotConfigured.message)
|
||||||
alert.addCancelAction(L10n.Global.ok)
|
alert.addCancelAction(L10n.Global.ok)
|
||||||
|
@ -45,26 +64,40 @@ class IssueReporter: NSObject {
|
||||||
|
|
||||||
self.viewController = viewController
|
self.viewController = viewController
|
||||||
|
|
||||||
let alert = Macros.alert(L10n.IssueReporter.title, L10n.IssueReporter.message)
|
if attachments.debugLog {
|
||||||
alert.addDefaultAction(L10n.IssueReporter.Buttons.accept) {
|
let alert = Macros.alert(L10n.IssueReporter.title, L10n.IssueReporter.message)
|
||||||
VPN.shared.requestDebugLog(fallback: AppConstants.Log.debugSnapshot) {
|
alert.addDefaultAction(L10n.IssueReporter.Buttons.accept) {
|
||||||
self.composeEmail(withDebugLog: $0)
|
VPN.shared.requestDebugLog(fallback: AppConstants.Log.debugSnapshot) {
|
||||||
|
self.composeEmail(withDebugLog: $0, configurationURL: attachments.configurationURL, description: attachments.description)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
alert.addCancelAction(L10n.Global.cancel)
|
||||||
|
viewController.present(alert, animated: true, completion: nil)
|
||||||
|
} else {
|
||||||
|
composeEmail(withDebugLog: nil, configurationURL: attachments.configurationURL, description: attachments.description)
|
||||||
}
|
}
|
||||||
alert.addCancelAction(L10n.Global.cancel)
|
|
||||||
viewController.present(alert, animated: true, completion: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func composeEmail(withDebugLog debugLog: String?) {
|
private func composeEmail(withDebugLog debugLog: String?, configurationURL: URL?, description: String?) {
|
||||||
let metadata = DebugLog(raw: "--").decoratedString()
|
let metadata = DebugLog(raw: "--").decoratedString()
|
||||||
|
|
||||||
let vc = MFMailComposeViewController()
|
let vc = MFMailComposeViewController()
|
||||||
vc.setToRecipients([AppConstants.IssueReporter.recipient])
|
vc.setToRecipients([AppConstants.IssueReporter.recipient])
|
||||||
vc.setSubject(L10n.IssueReporter.Email.subject(GroupConstants.App.name))
|
vc.setSubject(L10n.IssueReporter.Email.subject(GroupConstants.App.name))
|
||||||
vc.setMessageBody(L10n.IssueReporter.Email.body(metadata), isHTML: false)
|
vc.setMessageBody(L10n.IssueReporter.Email.body(description ?? L10n.IssueReporter.Email.description, metadata), isHTML: false)
|
||||||
if let raw = debugLog {
|
if let raw = debugLog {
|
||||||
let attachment = DebugLog(raw: raw).decoratedData()
|
let attachment = DebugLog(raw: raw).decoratedData()
|
||||||
vc.addAttachmentData(attachment, mimeType: AppConstants.IssueReporter.attachmentMIME, fileName: AppConstants.Log.debugFilename)
|
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) {
|
||||||
|
vc.addAttachmentData(attachment, mimeType: AppConstants.IssueReporter.MIME.configuration, fileName: AppConstants.IssueReporter.Filenames.configuration)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
vc.mailComposeDelegate = self
|
vc.mailComposeDelegate = self
|
||||||
vc.apply(Theme.current)
|
vc.apply(Theme.current)
|
||||||
|
|
|
@ -64,7 +64,7 @@ class DebugLogViewController: UIViewController {
|
||||||
}
|
}
|
||||||
let data = DebugLog(raw: raw).decoratedData()
|
let data = DebugLog(raw: raw).decoratedData()
|
||||||
|
|
||||||
let path = NSTemporaryDirectory().appending(AppConstants.Log.debugFilename)
|
let path = NSTemporaryDirectory().appending(AppConstants.IssueReporter.Filenames.debugLog)
|
||||||
let url = URL(fileURLWithPath: path)
|
let url = URL(fileURLWithPath: path)
|
||||||
do {
|
do {
|
||||||
try data.write(to: url)
|
try data.write(to: url)
|
||||||
|
|
|
@ -134,7 +134,7 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard {
|
||||||
profile.parameters = file.configuration
|
profile.parameters = file.configuration
|
||||||
|
|
||||||
guard !TransientStore.shared.service.containsProfile(profile) else {
|
guard !TransientStore.shared.service.containsProfile(profile) else {
|
||||||
let alert = Macros.alert(title, L10n.Wizards.Host.Alerts.existing)
|
let alert = Macros.alert(title, L10n.Wizards.Host.Alerts.Existing.message)
|
||||||
alert.addDefaultAction(L10n.Global.ok) {
|
alert.addDefaultAction(L10n.Global.ok) {
|
||||||
self.next(withProfile: profile)
|
self.next(withProfile: profile)
|
||||||
}
|
}
|
||||||
|
@ -159,8 +159,8 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard {
|
||||||
}
|
}
|
||||||
if let url = parsedFile?.url {
|
if let url = parsedFile?.url {
|
||||||
do {
|
do {
|
||||||
let savedUrl = try ProfileConfigurationFactory.shared.save(url: url, for: profile)
|
let savedURL = try TransientStore.shared.service.save(configurationURL: url, for: profile)
|
||||||
log.debug("Associated .ovpn configuration file to profile '\(profile.id)': \(savedUrl)")
|
log.debug("Associated .ovpn configuration file to profile '\(profile.id)': \(savedURL)")
|
||||||
} catch let e {
|
} catch let e {
|
||||||
log.error("Could not associate .ovpn configuration file to profile: \(e)")
|
log.error("Could not associate .ovpn configuration file to profile: \(e)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ class ServiceViewController: UIViewController, TableModelHost {
|
||||||
let vc = destination as? ConfigurationViewController
|
let vc = destination as? ConfigurationViewController
|
||||||
vc?.title = L10n.Service.Cells.Host.Parameters.caption
|
vc?.title = L10n.Service.Cells.Host.Parameters.caption
|
||||||
vc?.initialConfiguration = uncheckedHostProfile.parameters.sessionConfiguration
|
vc?.initialConfiguration = uncheckedHostProfile.parameters.sessionConfiguration
|
||||||
vc?.originalConfigurationURL = ProfileConfigurationFactory.shared.configurationURL(for: uncheckedHostProfile)
|
vc?.originalConfigurationURL = service.configurationURL(for: uncheckedHostProfile)
|
||||||
vc?.delegate = self
|
vc?.delegate = self
|
||||||
|
|
||||||
case .debugLogSegueIdentifier:
|
case .debugLogSegueIdentifier:
|
||||||
|
@ -361,7 +361,8 @@ class ServiceViewController: UIViewController, TableModelHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reportConnectivityIssue() {
|
private func reportConnectivityIssue() {
|
||||||
IssueReporter.shared.present(in: self)
|
let attach = IssueReporter.Attachments(debugLog: true, profile: uncheckedProfile)
|
||||||
|
IssueReporter.shared.present(in: self, withAttachments: attach)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Notifications
|
// MARK: Notifications
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */; };
|
0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */; };
|
||||||
0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B493F20FCFF990094784C /* Theme+Titles.swift */; };
|
0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B493F20FCFF990094784C /* Theme+Titles.swift */; };
|
||||||
0E2B494220FD16540094784C /* TransientStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B494120FD16540094784C /* TransientStore.swift */; };
|
0E2B494220FD16540094784C /* TransientStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B494120FD16540094784C /* TransientStore.swift */; };
|
||||||
0E2D11BA217DBEDE0096822C /* ProfileConfigurationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2D11B9217DBEDE0096822C /* ProfileConfigurationFactory.swift */; };
|
0E2D11BA217DBEDE0096822C /* ConnectionService+Configurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2D11B9217DBEDE0096822C /* ConnectionService+Configurations.swift */; };
|
||||||
0E39BCF0214B9EF10035E9DE /* WebServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCEF214B9EF10035E9DE /* WebServices.swift */; };
|
0E39BCF0214B9EF10035E9DE /* WebServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCEF214B9EF10035E9DE /* WebServices.swift */; };
|
||||||
0E39BCF3214DA9310035E9DE /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCF2214DA9310035E9DE /* AppConstants.swift */; };
|
0E39BCF3214DA9310035E9DE /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCF2214DA9310035E9DE /* AppConstants.swift */; };
|
||||||
0E3DA371215CB5BF00B40FC9 /* VersionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */; };
|
0E3DA371215CB5BF00B40FC9 /* VersionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */; };
|
||||||
|
@ -137,7 +137,7 @@
|
||||||
0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationViewController.swift; sourceTree = "<group>"; };
|
0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationViewController.swift; sourceTree = "<group>"; };
|
||||||
0E2B493F20FCFF990094784C /* Theme+Titles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Titles.swift"; sourceTree = "<group>"; };
|
0E2B493F20FCFF990094784C /* Theme+Titles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Titles.swift"; sourceTree = "<group>"; };
|
||||||
0E2B494120FD16540094784C /* TransientStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientStore.swift; sourceTree = "<group>"; };
|
0E2B494120FD16540094784C /* TransientStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientStore.swift; sourceTree = "<group>"; };
|
||||||
0E2D11B9217DBEDE0096822C /* ProfileConfigurationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileConfigurationFactory.swift; sourceTree = "<group>"; };
|
0E2D11B9217DBEDE0096822C /* ConnectionService+Configurations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConnectionService+Configurations.swift"; sourceTree = "<group>"; };
|
||||||
0E39BCEF214B9EF10035E9DE /* WebServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebServices.swift; sourceTree = "<group>"; };
|
0E39BCEF214B9EF10035E9DE /* WebServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebServices.swift; sourceTree = "<group>"; };
|
||||||
0E39BCF2214DA9310035E9DE /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = "<group>"; };
|
0E39BCF2214DA9310035E9DE /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = "<group>"; };
|
||||||
0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionViewController.swift; sourceTree = "<group>"; };
|
0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -382,12 +382,12 @@
|
||||||
0EBE3AA2213DC1B000BFA2F5 /* Profiles */,
|
0EBE3AA2213DC1B000BFA2F5 /* Profiles */,
|
||||||
0EBE3A9E213DC1A100BFA2F5 /* ConnectionProfile.swift */,
|
0EBE3A9E213DC1A100BFA2F5 /* ConnectionProfile.swift */,
|
||||||
0EBE3A9F213DC1A100BFA2F5 /* ConnectionService.swift */,
|
0EBE3A9F213DC1A100BFA2F5 /* ConnectionService.swift */,
|
||||||
|
0E2D11B9217DBEDE0096822C /* ConnectionService+Configurations.swift */,
|
||||||
0EBBE8F42182361700106008 /* ConnectionService+Migration.swift */,
|
0EBBE8F42182361700106008 /* ConnectionService+Migration.swift */,
|
||||||
0EDE8DE620C93945004C739C /* Credentials.swift */,
|
0EDE8DE620C93945004C739C /* Credentials.swift */,
|
||||||
0EC7F20420E24308004EA58E /* DebugLog.swift */,
|
0EC7F20420E24308004EA58E /* DebugLog.swift */,
|
||||||
0ED38AE621404F100004D387 /* EndpointDataSource.swift */,
|
0ED38AE621404F100004D387 /* EndpointDataSource.swift */,
|
||||||
0E89DFC4213DF7AE00741BA1 /* Preferences.swift */,
|
0E89DFC4213DF7AE00741BA1 /* Preferences.swift */,
|
||||||
0E2D11B9217DBEDE0096822C /* ProfileConfigurationFactory.swift */,
|
|
||||||
0E89DFC7213E8FC500741BA1 /* SessionProxy+Communication.swift */,
|
0E89DFC7213E8FC500741BA1 /* SessionProxy+Communication.swift */,
|
||||||
0E2B494120FD16540094784C /* TransientStore.swift */,
|
0E2B494120FD16540094784C /* TransientStore.swift */,
|
||||||
0E4C9CB820DB9BC600A0C59C /* TrustedNetworks.swift */,
|
0E4C9CB820DB9BC600A0C59C /* TrustedNetworks.swift */,
|
||||||
|
@ -842,7 +842,7 @@
|
||||||
0ED31C1220CF0ABA0027975F /* Infrastructure.swift in Sources */,
|
0ED31C1220CF0ABA0027975F /* Infrastructure.swift in Sources */,
|
||||||
0EC7F20520E24308004EA58E /* DebugLog.swift in Sources */,
|
0EC7F20520E24308004EA58E /* DebugLog.swift in Sources */,
|
||||||
0E4FD7E120D3E4C5002221FF /* MockVPNProvider.swift in Sources */,
|
0E4FD7E120D3E4C5002221FF /* MockVPNProvider.swift in Sources */,
|
||||||
0E2D11BA217DBEDE0096822C /* ProfileConfigurationFactory.swift in Sources */,
|
0E2D11BA217DBEDE0096822C /* ConnectionService+Configurations.swift in Sources */,
|
||||||
0EBE3A90213C6F4000BFA2F5 /* TrustPolicy.swift in Sources */,
|
0EBE3A90213C6F4000BFA2F5 /* TrustPolicy.swift in Sources */,
|
||||||
0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */,
|
0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */,
|
||||||
0E89DFC8213E8FC500741BA1 /* SessionProxy+Communication.swift in Sources */,
|
0E89DFC8213E8FC500741BA1 /* SessionProxy+Communication.swift in Sources */,
|
||||||
|
|
|
@ -51,9 +51,11 @@
|
||||||
"wizards.host.cells.title_input.caption" = "Title";
|
"wizards.host.cells.title_input.caption" = "Title";
|
||||||
"wizards.host.cells.title_input.placeholder" = "My Profile";
|
"wizards.host.cells.title_input.placeholder" = "My Profile";
|
||||||
"wizards.host.sections.existing.header" = "Existing profiles";
|
"wizards.host.sections.existing.header" = "Existing profiles";
|
||||||
"wizards.host.alerts.existing" = "A host profile with the same title already exists. Replace it?";
|
"wizards.host.alerts.existing.message" = "A host profile with the same title already exists. Replace it?";
|
||||||
"wizards.host.alerts.unsupported" = "The configuration file contains an unsupported option (%@).";
|
"wizards.host.alerts.missing.message" = "The configuration file lacks a required option (%@).";
|
||||||
"wizards.host.alerts.parsing" = "Unable to parse the provided configuration file (%@).";
|
"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";
|
||||||
|
|
||||||
"service.welcome.message" = "Welcome to Passepartout!\n\nUse the organizer to add a new profile.";
|
"service.welcome.message" = "Welcome to Passepartout!\n\nUse the organizer to add a new profile.";
|
||||||
"service.sections.general.header" = "General";
|
"service.sections.general.header" = "General";
|
||||||
|
@ -171,11 +173,12 @@
|
||||||
"vpn.errors.network" = "Network changed";
|
"vpn.errors.network" = "Network changed";
|
||||||
|
|
||||||
"issue_reporter.title" = "Report issue";
|
"issue_reporter.title" = "Report issue";
|
||||||
"issue_reporter.message" = "The debug log of your latest connections is crucial to resolve your connectivity issues and is completely anonymous.";
|
"issue_reporter.message" = "The debug log of your latest connections is crucial to resolve your connectivity issues and is completely anonymous.\n\nThe .ovpn configuration file, if any, is attached stripped of any sensitive data.\n\nPlease double check the email attachments if unsure.";
|
||||||
"issue_reporter.buttons.accept" = "I understand";
|
"issue_reporter.buttons.accept" = "I understand";
|
||||||
"issue_reporter.alerts.email_not_configured.message" = "No e-mail account is configured.";
|
"issue_reporter.alerts.email_not_configured.message" = "No e-mail account is configured.";
|
||||||
"issue_reporter.email.subject" = "%@ - Debug log";
|
"issue_reporter.email.subject" = "%@ - Report issue";
|
||||||
"issue_reporter.email.body" = "Hi,\n\ndescription of the issue:\n\n%@\n\nRegards";
|
"issue_reporter.email.body" = "Hi,\n\n%@\n\n%@\n\nRegards";
|
||||||
|
"issue_reporter.email.description" = "description of the issue:";
|
||||||
|
|
||||||
"about.title" = "About";
|
"about.title" = "About";
|
||||||
"about.sections.info.header" = "General";
|
"about.sections.info.header" = "General";
|
||||||
|
|
|
@ -96,19 +96,12 @@ class AppConstants {
|
||||||
|
|
||||||
static var debugSnapshot: () -> String = { TransientStore.shared.service.vpnLog }
|
static var debugSnapshot: () -> String = { TransientStore.shared.service.vpnLog }
|
||||||
|
|
||||||
static var debugFilename: String {
|
|
||||||
let fmt = DateFormatter()
|
|
||||||
fmt.dateFormat = "yyyyMMdd-HHmmss"
|
|
||||||
let iso = fmt.string(from: Date())
|
|
||||||
return "debug-\(iso).txt"
|
|
||||||
}
|
|
||||||
|
|
||||||
static let viewerRefreshInterval: TimeInterval = 3.0
|
static let viewerRefreshInterval: TimeInterval = 3.0
|
||||||
|
|
||||||
static func configure() {
|
static func configure() {
|
||||||
let console = ConsoleDestination()
|
let console = ConsoleDestination()
|
||||||
console.useNSLog = true
|
console.useNSLog = true
|
||||||
console.minLevel = .verbose
|
console.minLevel = .debug
|
||||||
SwiftyBeaver.addDestination(console)
|
SwiftyBeaver.addDestination(console)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +109,24 @@ class AppConstants {
|
||||||
class IssueReporter {
|
class IssueReporter {
|
||||||
static let recipient = "issues@\(Domain.name)"
|
static let recipient = "issues@\(Domain.name)"
|
||||||
|
|
||||||
static let attachmentMIME = "text/plain"
|
class Filenames {
|
||||||
|
static var debugLog: String {
|
||||||
|
let fmt = DateFormatter()
|
||||||
|
fmt.dateFormat = "yyyyMMdd-HHmmss"
|
||||||
|
let iso = fmt.string(from: Date())
|
||||||
|
return "debug-\(iso).txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
// static let configuration = "profile.ovpn"
|
||||||
|
static let configuration = "profile.ovpn.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
class MIME {
|
||||||
|
static let debugLog = "text/plain"
|
||||||
|
|
||||||
|
// static let configuration = "application/x-openvpn-profile"
|
||||||
|
static let configuration = "text/plain"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class URLs {
|
class URLs {
|
||||||
|
|
|
@ -30,9 +30,7 @@ enum ApplicationError: Error {
|
||||||
|
|
||||||
case missingCredentials
|
case missingCredentials
|
||||||
|
|
||||||
case missingCA
|
case missingConfiguration(option: String)
|
||||||
|
|
||||||
case emptyRemotes
|
|
||||||
|
|
||||||
case unsupportedConfiguration(option: String)
|
case unsupportedConfiguration(option: String)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
//
|
||||||
|
// ConnectionService+Configurations.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 10/22/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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension ConnectionService {
|
||||||
|
func save(configurationURL: URL, for profile: ConnectionProfile) throws -> URL {
|
||||||
|
let destinationURL = targetConfigurationURL(for: profile)
|
||||||
|
let fm = FileManager.default
|
||||||
|
try? fm.removeItem(at: destinationURL)
|
||||||
|
try fm.copyItem(at: configurationURL, to: destinationURL)
|
||||||
|
return destinationURL
|
||||||
|
}
|
||||||
|
|
||||||
|
func configurationURL(for profile: ConnectionProfile) -> URL? {
|
||||||
|
let url = targetConfigurationURL(for: profile)
|
||||||
|
guard FileManager.default.fileExists(atPath: url.path) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
private func targetConfigurationURL(for profile: ConnectionProfile) -> URL {
|
||||||
|
let contextURL = ConnectionService.ProfileKey(profile).contextURL(in: self)
|
||||||
|
return contextURL.appendingPathComponent("\(profile.id).ovpn")
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,19 +64,21 @@ class ConnectionService: Codable {
|
||||||
id = profile.id
|
id = profile.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func profileURL(in service: ConnectionService) -> URL {
|
func contextURL(in service: ConnectionService) -> URL {
|
||||||
let contextURL: URL
|
|
||||||
switch context {
|
switch context {
|
||||||
case .provider:
|
case .provider:
|
||||||
contextURL = service.providersURL
|
return service.providersURL
|
||||||
|
|
||||||
case .host:
|
case .host:
|
||||||
contextURL = service.hostsURL
|
return service.hostsURL
|
||||||
}
|
}
|
||||||
return ConnectionService.url(in: contextURL, forProfileId: id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func profileData(in service: ConnectionService) throws -> Data {
|
func profileURL(in service: ConnectionService) -> URL {
|
||||||
|
return ConnectionService.url(in: contextURL(in: service), forProfileId: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func profileData(in service: ConnectionService) throws -> Data {
|
||||||
return try Data(contentsOf: profileURL(in: service))
|
return try Data(contentsOf: profileURL(in: service))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
//
|
|
||||||
// ProfileConfigurationFactory.swift
|
|
||||||
// Passepartout
|
|
||||||
//
|
|
||||||
// Created by Davide De Rosa on 10/22/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 <http://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
protocol ProfileConfigurationSource {
|
|
||||||
var id: String { get }
|
|
||||||
|
|
||||||
var profileDirectory: String { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ProfileConfigurationSource {
|
|
||||||
var profileConfigurationPath: String {
|
|
||||||
return "\(profileDirectory)/\(id).ovpn"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ProviderConnectionProfile: ProfileConfigurationSource {
|
|
||||||
var profileDirectory: String {
|
|
||||||
return AppConstants.Store.providersDirectory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension HostConnectionProfile: ProfileConfigurationSource {
|
|
||||||
var profileDirectory: String {
|
|
||||||
return AppConstants.Store.hostsDirectory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProfileConfigurationFactory {
|
|
||||||
static let shared = ProfileConfigurationFactory()
|
|
||||||
|
|
||||||
private let configurationsPath: URL
|
|
||||||
|
|
||||||
private init() {
|
|
||||||
let fm = FileManager.default
|
|
||||||
configurationsPath = fm.userURL(for: .documentDirectory, appending: nil)
|
|
||||||
try? fm.createDirectory(at: configurationsPath, withIntermediateDirectories: false, attributes: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func save(url: URL, for profile: ProfileConfigurationSource) throws -> URL {
|
|
||||||
let savedUrl = targetConfigurationURL(for: profile)
|
|
||||||
let fm = FileManager.default
|
|
||||||
try? fm.removeItem(at: savedUrl)
|
|
||||||
try fm.copyItem(at: url, to: savedUrl)
|
|
||||||
return savedUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
func configurationURL(for profile: ProfileConfigurationSource) -> URL? {
|
|
||||||
let url = targetConfigurationURL(for: profile)
|
|
||||||
guard FileManager.default.fileExists(atPath: url.path) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
private func targetConfigurationURL(for profile: ProfileConfigurationSource) -> URL {
|
|
||||||
return configurationsPath.appendingPathComponent(profile.profileConfigurationPath)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -301,7 +301,7 @@ internal enum L10n {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum IssueReporter {
|
internal enum IssueReporter {
|
||||||
/// The debug log of your latest connections is crucial to resolve your connectivity issues and is completely anonymous.
|
/// The debug log of your latest connections is crucial to resolve your connectivity issues and is completely anonymous.\n\nThe .ovpn configuration file, if any, is attached stripped of any sensitive data.\n\nPlease double check the email attachments if unsure.
|
||||||
internal static let message = L10n.tr("Localizable", "issue_reporter.message")
|
internal static let message = L10n.tr("Localizable", "issue_reporter.message")
|
||||||
/// Report issue
|
/// Report issue
|
||||||
internal static let title = L10n.tr("Localizable", "issue_reporter.title")
|
internal static let title = L10n.tr("Localizable", "issue_reporter.title")
|
||||||
|
@ -320,11 +320,13 @@ internal enum L10n {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum Email {
|
internal enum Email {
|
||||||
/// Hi,\n\ndescription of the issue:\n\n%@\n\nRegards
|
/// Hi,\n\n%@\n\n%@\n\nRegards
|
||||||
internal static func body(_ p1: String) -> String {
|
internal static func body(_ p1: String, _ p2: String) -> String {
|
||||||
return L10n.tr("Localizable", "issue_reporter.email.body", p1)
|
return L10n.tr("Localizable", "issue_reporter.email.body", p1, p2)
|
||||||
}
|
}
|
||||||
/// %@ - Debug log
|
/// description of the issue:
|
||||||
|
internal static let description = L10n.tr("Localizable", "issue_reporter.email.description")
|
||||||
|
/// %@ - Report issue
|
||||||
internal static func subject(_ p1: String) -> String {
|
internal static func subject(_ p1: String) -> String {
|
||||||
return L10n.tr("Localizable", "issue_reporter.email.subject", p1)
|
return L10n.tr("Localizable", "issue_reporter.email.subject", p1)
|
||||||
}
|
}
|
||||||
|
@ -731,15 +733,36 @@ internal enum L10n {
|
||||||
internal enum Host {
|
internal enum Host {
|
||||||
|
|
||||||
internal enum Alerts {
|
internal enum Alerts {
|
||||||
/// A host profile with the same title already exists. Replace it?
|
|
||||||
internal static let existing = L10n.tr("Localizable", "wizards.host.alerts.existing")
|
internal enum Buttons {
|
||||||
/// Unable to parse the provided configuration file (%@).
|
/// Report an issue
|
||||||
internal static func parsing(_ p1: String) -> String {
|
internal static let report = L10n.tr("Localizable", "wizards.host.alerts.buttons.report")
|
||||||
return L10n.tr("Localizable", "wizards.host.alerts.parsing", p1)
|
|
||||||
}
|
}
|
||||||
/// The configuration file contains an unsupported option (%@).
|
|
||||||
internal static func unsupported(_ p1: String) -> String {
|
internal enum Existing {
|
||||||
return L10n.tr("Localizable", "wizards.host.alerts.unsupported", p1)
|
/// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ extension TunnelKitProvider.Configuration {
|
||||||
static let blockEnd = Utils.regex("^<\\/[\\w\\-]+>")
|
static let blockEnd = Utils.regex("^<\\/[\\w\\-]+>")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func parsed(from url: URL) throws -> (String, TunnelKitProvider.Configuration) {
|
static func parsed(from url: URL, stripped: UnsafeMutablePointer<[String]>? = nil) throws -> (String, TunnelKitProvider.Configuration) {
|
||||||
let lines = try String(contentsOf: url).trimmedLines()
|
let lines = try String(contentsOf: url).trimmedLines()
|
||||||
|
|
||||||
var defaultProto: TunnelKitProvider.SocketType?
|
var defaultProto: TunnelKitProvider.SocketType?
|
||||||
|
@ -90,7 +90,16 @@ extension TunnelKitProvider.Configuration {
|
||||||
for line in lines {
|
for line in lines {
|
||||||
log.verbose(line)
|
log.verbose(line)
|
||||||
|
|
||||||
|
var isHandled = false
|
||||||
|
var strippedLine = line
|
||||||
|
defer {
|
||||||
|
if isHandled {
|
||||||
|
stripped?.pointee.append(strippedLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Regex.blockBegin.enumerateComponents(in: line) {
|
Regex.blockBegin.enumerateComponents(in: line) {
|
||||||
|
isHandled = true
|
||||||
let tag = $0.first!
|
let tag = $0.first!
|
||||||
let from = tag.index(after: tag.startIndex)
|
let from = tag.index(after: tag.startIndex)
|
||||||
let to = tag.index(before: tag.endIndex)
|
let to = tag.index(before: tag.endIndex)
|
||||||
|
@ -99,6 +108,7 @@ extension TunnelKitProvider.Configuration {
|
||||||
currentBlock = []
|
currentBlock = []
|
||||||
}
|
}
|
||||||
Regex.blockEnd.enumerateComponents(in: line) {
|
Regex.blockEnd.enumerateComponents(in: line) {
|
||||||
|
isHandled = true
|
||||||
let tag = $0.first!
|
let tag = $0.first!
|
||||||
let from = tag.index(tag.startIndex, offsetBy: 2)
|
let from = tag.index(tag.startIndex, offsetBy: 2)
|
||||||
let to = tag.index(before: tag.endIndex)
|
let to = tag.index(before: tag.endIndex)
|
||||||
|
@ -140,6 +150,7 @@ extension TunnelKitProvider.Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
Regex.proto.enumerateArguments(in: line) {
|
Regex.proto.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
guard let str = $0.first else {
|
guard let str = $0.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -149,26 +160,35 @@ extension TunnelKitProvider.Configuration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Regex.port.enumerateArguments(in: line) {
|
Regex.port.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
guard let str = $0.first else {
|
guard let str = $0.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defaultPort = UInt16(str)
|
defaultPort = UInt16(str)
|
||||||
}
|
}
|
||||||
Regex.remote.enumerateArguments(in: line) {
|
Regex.remote.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
guard let hostname = $0.first else {
|
guard let hostname = $0.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var port: UInt16?
|
var port: UInt16?
|
||||||
var proto: TunnelKitProvider.SocketType?
|
var proto: TunnelKitProvider.SocketType?
|
||||||
|
var strippedComponents = ["remote", "<hostname>"]
|
||||||
if $0.count > 1 {
|
if $0.count > 1 {
|
||||||
port = UInt16($0[1])
|
port = UInt16($0[1])
|
||||||
|
strippedComponents.append($0[1])
|
||||||
}
|
}
|
||||||
if $0.count > 2 {
|
if $0.count > 2 {
|
||||||
proto = TunnelKitProvider.SocketType(protoString: $0[2])
|
proto = TunnelKitProvider.SocketType(protoString: $0[2])
|
||||||
|
strippedComponents.append($0[2])
|
||||||
}
|
}
|
||||||
remotes.append((hostname, port, proto))
|
remotes.append((hostname, port, proto))
|
||||||
|
|
||||||
|
// replace private data
|
||||||
|
strippedLine = strippedComponents.joined(separator: " ")
|
||||||
}
|
}
|
||||||
Regex.cipher.enumerateArguments(in: line) {
|
Regex.cipher.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
guard let rawValue = $0.first else {
|
guard let rawValue = $0.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -178,6 +198,7 @@ extension TunnelKitProvider.Configuration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Regex.auth.enumerateArguments(in: line) {
|
Regex.auth.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
guard let rawValue = $0.first else {
|
guard let rawValue = $0.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -187,24 +208,29 @@ extension TunnelKitProvider.Configuration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Regex.compLZO.enumerateComponents(in: line) { _ in
|
Regex.compLZO.enumerateComponents(in: line) { _ in
|
||||||
|
isHandled = true
|
||||||
compressionFraming = .compLZO
|
compressionFraming = .compLZO
|
||||||
}
|
}
|
||||||
Regex.compress.enumerateComponents(in: line) { _ in
|
Regex.compress.enumerateComponents(in: line) { _ in
|
||||||
|
isHandled = true
|
||||||
compressionFraming = .compress
|
compressionFraming = .compress
|
||||||
}
|
}
|
||||||
Regex.keyDirection.enumerateArguments(in: line) {
|
Regex.keyDirection.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
guard let arg = $0.first, let value = Int(arg) else {
|
guard let arg = $0.first, let value = Int(arg) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
keyDirection = StaticKey.Direction(rawValue: value)
|
keyDirection = StaticKey.Direction(rawValue: value)
|
||||||
}
|
}
|
||||||
Regex.ping.enumerateArguments(in: line) {
|
Regex.ping.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
guard let arg = $0.first else {
|
guard let arg = $0.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
keepAliveSeconds = TimeInterval(arg)
|
keepAliveSeconds = TimeInterval(arg)
|
||||||
}
|
}
|
||||||
Regex.renegSec.enumerateArguments(in: line) {
|
Regex.renegSec.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
guard let arg = $0.first else {
|
guard let arg = $0.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -219,6 +245,9 @@ extension TunnelKitProvider.Configuration {
|
||||||
Regex.externalFiles.enumerateArguments(in: line) { (_) in
|
Regex.externalFiles.enumerateArguments(in: line) { (_) in
|
||||||
unsupportedError = ApplicationError.unsupportedConfiguration(option: "external file: \"\(line)\"")
|
unsupportedError = ApplicationError.unsupportedConfiguration(option: "external file: \"\(line)\"")
|
||||||
}
|
}
|
||||||
|
if line.contains("mtu") || line.contains("mssfix") {
|
||||||
|
isHandled = true
|
||||||
|
}
|
||||||
|
|
||||||
if let error = unsupportedError {
|
if let error = unsupportedError {
|
||||||
throw error
|
throw error
|
||||||
|
@ -226,13 +255,13 @@ extension TunnelKitProvider.Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let ca = optCA else {
|
guard let ca = optCA else {
|
||||||
throw ApplicationError.missingCA
|
throw ApplicationError.missingConfiguration(option: "ca")
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: only reads first remote
|
// XXX: only reads first remote
|
||||||
// hostnames = remotes.map { $0.0 }
|
// hostnames = remotes.map { $0.0 }
|
||||||
guard !remotes.isEmpty else {
|
guard !remotes.isEmpty else {
|
||||||
throw ApplicationError.emptyRemotes
|
throw ApplicationError.missingConfiguration(option: "remote")
|
||||||
}
|
}
|
||||||
let hostname = remotes[0].0
|
let hostname = remotes[0].0
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,13 @@ class FileConfigurationTests: XCTestCase {
|
||||||
XCTAssertEqual(cfg.sessionConfiguration.digest, .sha1)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
private func url(withName name: String) -> URL {
|
private func url(withName name: String) -> URL {
|
||||||
return Bundle(for: FileConfigurationTests.self).url(forResource: name, withExtension: "ovpn")!
|
return Bundle(for: FileConfigurationTests.self).url(forResource: name, withExtension: "ovpn")!
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,14 +70,12 @@ Passepartout can import .ovpn configuration files. This way you can fine-tune en
|
||||||
Unsupported:
|
Unsupported:
|
||||||
|
|
||||||
- UDP fragmentation, i.e. `--fragment`
|
- UDP fragmentation, i.e. `--fragment`
|
||||||
|
|
||||||
Unsupported (probably ever):
|
|
||||||
|
|
||||||
- Compression
|
- Compression
|
||||||
- `--comp-lzo` other than `no`
|
- `--comp-lzo` other than `no`
|
||||||
- `--compress` other than empty
|
- `--compress` other than empty
|
||||||
- Proxy
|
- Proxy
|
||||||
- External file references (inline `<block>` only)
|
- External file references (inline `<block>` only)
|
||||||
|
- Encrypted certificate private key (will raise error TunnelKitNative Code=205)
|
||||||
|
|
||||||
Ignored:
|
Ignored:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue