Merge pull request #13 from keeshux/attach-ovpn-to-report

Attach .ovpn to connectivity issue report
This commit is contained in:
Davide De Rosa 2018-10-27 09:46:00 +02:00 committed by GitHub
commit 02c8e7b6ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 241 additions and 152 deletions

View File

@ -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/),
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)
### Changed

View File

@ -118,17 +118,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
}
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 alert = Macros.alert(L10n.Organizer.Sections.Hosts.header, L10n.Wizards.Host.Alerts.unsupported(option))
alert.addCancelAction(L10n.Global.ok)
root.present(alert, animated: true, completion: nil)
let message = L10n.Wizards.Host.Alerts.Unsupported.message(option)
alertConfigurationImportError(url: url, in: root, withMessage: message)
} catch let e {
let alert = Macros.alert(L10n.Organizer.Sections.Hosts.header, L10n.Wizards.Host.Alerts.parsing(e.localizedDescription))
alert.addCancelAction(L10n.Global.ok)
root.present(alert, animated: true, completion: nil)
let message = L10n.Wizards.Host.Alerts.Parsing.message(e.localizedDescription)
alertConfigurationImportError(url: url, in: root, withMessage: message)
}
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 {

View File

@ -24,9 +24,28 @@
//
import Foundation
import TunnelKit
import MessageUI
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()
private weak var viewController: UIViewController?
@ -35,7 +54,7 @@ class IssueReporter: NSObject {
super.init()
}
func present(in viewController: UIViewController) {
func present(in viewController: UIViewController, withAttachments attachments: Attachments) {
guard MFMailComposeViewController.canSendMail() else {
let alert = Macros.alert(L10n.IssueReporter.title, L10n.IssueReporter.Alerts.EmailNotConfigured.message)
alert.addCancelAction(L10n.Global.ok)
@ -45,26 +64,40 @@ class IssueReporter: NSObject {
self.viewController = viewController
let alert = Macros.alert(L10n.IssueReporter.title, L10n.IssueReporter.message)
alert.addDefaultAction(L10n.IssueReporter.Buttons.accept) {
VPN.shared.requestDebugLog(fallback: AppConstants.Log.debugSnapshot) {
self.composeEmail(withDebugLog: $0)
if attachments.debugLog {
let alert = Macros.alert(L10n.IssueReporter.title, L10n.IssueReporter.message)
alert.addDefaultAction(L10n.IssueReporter.Buttons.accept) {
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 vc = MFMailComposeViewController()
vc.setToRecipients([AppConstants.IssueReporter.recipient])
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 {
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.apply(Theme.current)

View File

@ -64,7 +64,7 @@ class DebugLogViewController: UIViewController {
}
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)
do {
try data.write(to: url)

View File

@ -134,7 +134,7 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard {
profile.parameters = file.configuration
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) {
self.next(withProfile: profile)
}
@ -159,8 +159,8 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard {
}
if let url = parsedFile?.url {
do {
let savedUrl = try ProfileConfigurationFactory.shared.save(url: url, for: profile)
log.debug("Associated .ovpn configuration file to profile '\(profile.id)': \(savedUrl)")
let savedURL = try TransientStore.shared.service.save(configurationURL: url, for: profile)
log.debug("Associated .ovpn configuration file to profile '\(profile.id)': \(savedURL)")
} catch let e {
log.error("Could not associate .ovpn configuration file to profile: \(e)")
}

View File

@ -154,7 +154,7 @@ class ServiceViewController: UIViewController, TableModelHost {
let vc = destination as? ConfigurationViewController
vc?.title = L10n.Service.Cells.Host.Parameters.caption
vc?.initialConfiguration = uncheckedHostProfile.parameters.sessionConfiguration
vc?.originalConfigurationURL = ProfileConfigurationFactory.shared.configurationURL(for: uncheckedHostProfile)
vc?.originalConfigurationURL = service.configurationURL(for: uncheckedHostProfile)
vc?.delegate = self
case .debugLogSegueIdentifier:
@ -361,7 +361,8 @@ class ServiceViewController: UIViewController, TableModelHost {
}
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

View File

@ -20,7 +20,7 @@
0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.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 */; };
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 */; };
0E39BCF3214DA9310035E9DE /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCF2214DA9310035E9DE /* AppConstants.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>"; };
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>"; };
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>"; };
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>"; };
@ -382,12 +382,12 @@
0EBE3AA2213DC1B000BFA2F5 /* Profiles */,
0EBE3A9E213DC1A100BFA2F5 /* ConnectionProfile.swift */,
0EBE3A9F213DC1A100BFA2F5 /* ConnectionService.swift */,
0E2D11B9217DBEDE0096822C /* ConnectionService+Configurations.swift */,
0EBBE8F42182361700106008 /* ConnectionService+Migration.swift */,
0EDE8DE620C93945004C739C /* Credentials.swift */,
0EC7F20420E24308004EA58E /* DebugLog.swift */,
0ED38AE621404F100004D387 /* EndpointDataSource.swift */,
0E89DFC4213DF7AE00741BA1 /* Preferences.swift */,
0E2D11B9217DBEDE0096822C /* ProfileConfigurationFactory.swift */,
0E89DFC7213E8FC500741BA1 /* SessionProxy+Communication.swift */,
0E2B494120FD16540094784C /* TransientStore.swift */,
0E4C9CB820DB9BC600A0C59C /* TrustedNetworks.swift */,
@ -842,7 +842,7 @@
0ED31C1220CF0ABA0027975F /* Infrastructure.swift in Sources */,
0EC7F20520E24308004EA58E /* DebugLog.swift in Sources */,
0E4FD7E120D3E4C5002221FF /* MockVPNProvider.swift in Sources */,
0E2D11BA217DBEDE0096822C /* ProfileConfigurationFactory.swift in Sources */,
0E2D11BA217DBEDE0096822C /* ConnectionService+Configurations.swift in Sources */,
0EBE3A90213C6F4000BFA2F5 /* TrustPolicy.swift in Sources */,
0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */,
0E89DFC8213E8FC500741BA1 /* SessionProxy+Communication.swift in Sources */,

View File

@ -51,9 +51,11 @@
"wizards.host.cells.title_input.caption" = "Title";
"wizards.host.cells.title_input.placeholder" = "My Profile";
"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.unsupported" = "The configuration file contains an unsupported option (%@).";
"wizards.host.alerts.parsing" = "Unable to parse the provided configuration file (%@).";
"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";
"service.welcome.message" = "Welcome to Passepartout!\n\nUse the organizer to add a new profile.";
"service.sections.general.header" = "General";
@ -171,11 +173,12 @@
"vpn.errors.network" = "Network changed";
"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.alerts.email_not_configured.message" = "No e-mail account is configured.";
"issue_reporter.email.subject" = "%@ - Debug log";
"issue_reporter.email.body" = "Hi,\n\ndescription of the issue:\n\n%@\n\nRegards";
"issue_reporter.email.subject" = "%@ - Report issue";
"issue_reporter.email.body" = "Hi,\n\n%@\n\n%@\n\nRegards";
"issue_reporter.email.description" = "description of the issue:";
"about.title" = "About";
"about.sections.info.header" = "General";

View File

@ -96,19 +96,12 @@ class AppConstants {
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 func configure() {
let console = ConsoleDestination()
console.useNSLog = true
console.minLevel = .verbose
console.minLevel = .debug
SwiftyBeaver.addDestination(console)
}
}
@ -116,7 +109,24 @@ class AppConstants {
class IssueReporter {
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 {

View File

@ -30,10 +30,8 @@ enum ApplicationError: Error {
case missingCredentials
case missingCA
case missingConfiguration(option: String)
case emptyRemotes
case unsupportedConfiguration(option: String)
case migration

View File

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

View File

@ -64,19 +64,21 @@ class ConnectionService: Codable {
id = profile.id
}
fileprivate func profileURL(in service: ConnectionService) -> URL {
let contextURL: URL
func contextURL(in service: ConnectionService) -> URL {
switch context {
case .provider:
contextURL = service.providersURL
return service.providersURL
case .host:
contextURL = service.hostsURL
return service.hostsURL
}
return ConnectionService.url(in: contextURL, forProfileId: id)
}
func profileURL(in service: ConnectionService) -> URL {
return ConnectionService.url(in: contextURL(in: service), forProfileId: id)
}
fileprivate func profileData(in service: ConnectionService) throws -> Data {
func profileData(in service: ConnectionService) throws -> Data {
return try Data(contentsOf: profileURL(in: service))
}

View File

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

View File

@ -301,7 +301,7 @@ internal enum L10n {
}
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")
/// Report issue
internal static let title = L10n.tr("Localizable", "issue_reporter.title")
@ -320,11 +320,13 @@ internal enum L10n {
}
internal enum Email {
/// Hi,\n\ndescription of the issue:\n\n%@\n\nRegards
internal static func body(_ p1: String) -> String {
return L10n.tr("Localizable", "issue_reporter.email.body", p1)
/// Hi,\n\n%@\n\n%@\n\nRegards
internal static func body(_ p1: String, _ p2: String) -> String {
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 {
return L10n.tr("Localizable", "issue_reporter.email.subject", p1)
}
@ -731,15 +733,36 @@ internal enum L10n {
internal enum Host {
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")
/// Unable to parse the provided configuration file (%@).
internal static func parsing(_ p1: String) -> String {
return L10n.tr("Localizable", "wizards.host.alerts.parsing", p1)
internal enum Buttons {
/// Report an issue
internal static let report = L10n.tr("Localizable", "wizards.host.alerts.buttons.report")
}
/// The configuration file contains an unsupported option (%@).
internal static func unsupported(_ p1: String) -> String {
return L10n.tr("Localizable", "wizards.host.alerts.unsupported", p1)
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)
}
}
}

View File

@ -62,7 +62,7 @@ extension TunnelKitProvider.Configuration {
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()
var defaultProto: TunnelKitProvider.SocketType?
@ -90,7 +90,16 @@ extension TunnelKitProvider.Configuration {
for line in lines {
log.verbose(line)
var isHandled = false
var strippedLine = line
defer {
if isHandled {
stripped?.pointee.append(strippedLine)
}
}
Regex.blockBegin.enumerateComponents(in: line) {
isHandled = true
let tag = $0.first!
let from = tag.index(after: tag.startIndex)
let to = tag.index(before: tag.endIndex)
@ -99,6 +108,7 @@ extension TunnelKitProvider.Configuration {
currentBlock = []
}
Regex.blockEnd.enumerateComponents(in: line) {
isHandled = true
let tag = $0.first!
let from = tag.index(tag.startIndex, offsetBy: 2)
let to = tag.index(before: tag.endIndex)
@ -138,8 +148,9 @@ extension TunnelKitProvider.Configuration {
currentBlock.append(line)
continue
}
Regex.proto.enumerateArguments(in: line) {
isHandled = true
guard let str = $0.first else {
return
}
@ -149,26 +160,35 @@ extension TunnelKitProvider.Configuration {
}
}
Regex.port.enumerateArguments(in: line) {
isHandled = true
guard let str = $0.first else {
return
}
defaultPort = UInt16(str)
}
Regex.remote.enumerateArguments(in: line) {
isHandled = true
guard let hostname = $0.first else {
return
}
var port: UInt16?
var proto: TunnelKitProvider.SocketType?
var strippedComponents = ["remote", "<hostname>"]
if $0.count > 1 {
port = UInt16($0[1])
strippedComponents.append($0[1])
}
if $0.count > 2 {
proto = TunnelKitProvider.SocketType(protoString: $0[2])
strippedComponents.append($0[2])
}
remotes.append((hostname, port, proto))
// replace private data
strippedLine = strippedComponents.joined(separator: " ")
}
Regex.cipher.enumerateArguments(in: line) {
isHandled = true
guard let rawValue = $0.first else {
return
}
@ -178,6 +198,7 @@ extension TunnelKitProvider.Configuration {
}
}
Regex.auth.enumerateArguments(in: line) {
isHandled = true
guard let rawValue = $0.first else {
return
}
@ -187,24 +208,29 @@ extension TunnelKitProvider.Configuration {
}
}
Regex.compLZO.enumerateComponents(in: line) { _ in
isHandled = true
compressionFraming = .compLZO
}
Regex.compress.enumerateComponents(in: line) { _ in
isHandled = true
compressionFraming = .compress
}
Regex.keyDirection.enumerateArguments(in: line) {
isHandled = true
guard let arg = $0.first, let value = Int(arg) else {
return
}
keyDirection = StaticKey.Direction(rawValue: value)
}
Regex.ping.enumerateArguments(in: line) {
isHandled = true
guard let arg = $0.first else {
return
}
keepAliveSeconds = TimeInterval(arg)
}
Regex.renegSec.enumerateArguments(in: line) {
isHandled = true
guard let arg = $0.first else {
return
}
@ -219,6 +245,9 @@ extension TunnelKitProvider.Configuration {
Regex.externalFiles.enumerateArguments(in: line) { (_) in
unsupportedError = ApplicationError.unsupportedConfiguration(option: "external file: \"\(line)\"")
}
if line.contains("mtu") || line.contains("mssfix") {
isHandled = true
}
if let error = unsupportedError {
throw error
@ -226,13 +255,13 @@ extension TunnelKitProvider.Configuration {
}
guard let ca = optCA else {
throw ApplicationError.missingCA
throw ApplicationError.missingConfiguration(option: "ca")
}
// XXX: only reads first remote
// hostnames = remotes.map { $0.0 }
guard !remotes.isEmpty else {
throw ApplicationError.emptyRemotes
throw ApplicationError.missingConfiguration(option: "remote")
}
let hostname = remotes[0].0

View File

@ -44,6 +44,13 @@ class FileConfigurationTests: XCTestCase {
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 {
return Bundle(for: FileConfigurationTests.self).url(forResource: name, withExtension: "ovpn")!
}

View File

@ -70,14 +70,12 @@ Passepartout can import .ovpn configuration files. This way you can fine-tune en
Unsupported:
- UDP fragmentation, i.e. `--fragment`
Unsupported (probably ever):
- Compression
- `--comp-lzo` other than `no`
- `--compress` other than empty
- Proxy
- External file references (inline `<block>` only)
- Encrypted certificate private key (will raise error TunnelKitNative Code=205)
Ignored: