Working on PAC support

This commit is contained in:
Mikhail Churbanov 2018-05-24 01:37:02 +03:00
parent 951d206600
commit 442ae5ce94
2 changed files with 62 additions and 51 deletions

View File

@ -1,5 +1,5 @@
import XCTest
import ProxyResolver
@testable import ProxyResolver
class Tests: XCTestCase {
@ -12,8 +12,17 @@ class Tests: XCTestCase {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testGetSystemConfigProxies() {
let proxy = ProxyResolver()
let config = proxy.getSystemConfigProxies(for: URL(string: "http://google.com")!)
XCTAssertNotNil(config)
let firstProxyConfig = config!.first
XCTAssertNotNil(firstProxyConfig)
XCTAssertTrue(firstProxyConfig!.keys.contains(kCFProxyTypeKey))
}
func testExample() {
func testDummyResolve() {
// This is an example of a functional test case.
let expectation = XCTestExpectation(description: "Completion called")
let proxy = ProxyResolver()
@ -24,12 +33,5 @@ class Tests: XCTestCase {
wait(for: [expectation], timeout: 10.0)
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure() {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -69,7 +69,7 @@ public typealias ProxiesResolutionCompletion = (ResolutionResult<[Proxy]>) -> Vo
// MARK: - ProxyResolver class
public class ProxyResolver {
public final class ProxyResolver {
private let supportedAutoConfigUrlShemes = ["http", "https"]
private let shemeNormalizationRules = ["ws": "http",
@ -111,29 +111,21 @@ public class ProxyResolver {
resolveProxy(from: firstProxyConfig, for: normalizedUrl, completion: tryNextOnErrorCompletion)
}
public func resolveAll(for url: URL, completion: @escaping ProxiesResolutionCompletion) {
guard let normalizedUrl = urlWithNormalizedSheme(from: url) else { return }
guard let proxies = getSystemConfigProxies(for: normalizedUrl) else { return }
// resolveProxies(from: proxies, for: url, completion: completion)
}
// public func resolveAll(for url: URL, completion: @escaping ProxiesResolutionCompletion) {
// guard let normalizedUrl = urlWithNormalizedSheme(from: url) else { return }
// guard let proxies = getSystemConfigProxies(for: normalizedUrl) else { return }
// }
// MARK: Private Methods
// MARK: Internal Methods
private func getSystemConfigProxies(for url: URL) -> [[CFString: AnyObject]]? {
func getSystemConfigProxies(for url: URL) -> [[CFString: AnyObject]]? {
guard let systemSettings = CFNetworkCopySystemProxySettings()?.takeRetainedValue() else { return nil }
let cfProxies = CFNetworkCopyProxiesForURL(url as CFURL, systemSettings).takeRetainedValue()
return cfProxies as? [[CFString: AnyObject]]
}
private func resolveProxies(from proxies: [CFDictionary], for url: URL, completion: @escaping ProxyResolutionCompletion) {
for proxyConfig in proxies.compactMap({ $0 as? [CFString: AnyObject] }) {
print (proxyConfig)
// getProxy(from: proxyConfig, for: url)
}
}
private func resolveProxy(from config: [CFString: AnyObject], for url: URL,
completion: @escaping ProxyResolutionCompletion) {
func resolveProxy(from config: [CFString: AnyObject], for url: URL,
completion: @escaping ProxyResolutionCompletion) {
guard let proxyTypeValue = config[kCFProxyTypeKey] else {
let error = ProxyResolutionError.unexpectedError(nil)
@ -150,23 +142,51 @@ public class ProxyResolver {
switch proxyType {
case .autoConfigurationUrl:
guard let cfAutoConfigUrl = config[kCFProxyAutoConfigurationURLKey],
let urlString = cfAutoConfigUrl as? String,
let scriptUrl = URL(string: urlString)
let scriptUrlString = cfAutoConfigUrl as? String,
let scriptUrl = URL(string: scriptUrlString)
else {
// TODO: proper error handling
let error = ProxyResolutionError.unexpectedError(nil)
completion(.failure(error))
return
}
fetchPacScript(from: scriptUrl) { scriptContent, error in
guard let scriptContent = scriptContent else { return }
self.executePac(script: scriptContent, for: url)
guard let scriptContent = scriptContent else {
// TODO: proper error handling
let error = ProxyResolutionError.unexpectedError(nil)
completion(.failure(error))
return
}
// TODO: process all, first is for now
guard let pacProxyConfig = self.executePac(script: scriptContent, for: url)?.first else {
// TODO: proper error handling
let error = ProxyResolutionError.unexpectedError(nil)
completion(.failure(error))
return
}
// TODO: check if recursion here is possible?
self.resolveProxy(from: pacProxyConfig, for: url, completion: completion)
}
case .autoConfigurationScript:
guard let cfAutoConfigScript = config[kCFProxyAutoConfigurationJavaScriptKey],
let scriptContent = cfAutoConfigScript as? String
else {
// TODO: proper error handling
let error = ProxyResolutionError.unexpectedError(nil)
completion(.failure(error))
return
}
executePac(script: scriptContent, for: url)
// TODO: process all, first is for now
// TODO: check if code for pac should be moved out (same used after pac fetch above)
guard let pacProxyConfig = self.executePac(script: scriptContent, for: url)?.first else {
// TODO: proper error handling
let error = ProxyResolutionError.unexpectedError(nil)
completion(.failure(error))
return
}
// TODO: check if recursion here is possible?
self.resolveProxy(from: pacProxyConfig, for: url, completion: completion)
case .http, .https, .socks:
guard let cfProxyHost = config[kCFProxyHostNameKey],
@ -186,15 +206,13 @@ public class ProxyResolver {
}
}
private func fetchPacScript(from url: URL, completion: @escaping (String?, Error?) -> Void) {
func fetchPacScript(from url: URL, completion: @escaping (String?, Error?) -> Void) {
if url.isFileURL, let scriptContents = try? String(contentsOfFile: url.absoluteString) {
completion(scriptContents, nil)
return
}
guard let scheme = url.scheme?.lowercased(),
supportedAutoConfigUrlShemes.contains(scheme)
else {
guard let scheme = url.scheme?.lowercased(), supportedAutoConfigUrlShemes.contains(scheme) else {
completion(nil, nil)
return
}
@ -210,30 +228,21 @@ public class ProxyResolver {
task.resume()
}
private func executePac(script: String, for url: URL) {
// From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html
// Work around <rdar://problem/5530166>. This dummy call to
// CFNetworkCopyProxiesForURL initialise some state within CFNetwork
// that is required by CFNetworkCopyProxiesForAutoConfigurationScript.
_ = CFNetworkCopyProxiesForURL(url as CFURL, NSDictionary()).takeRetainedValue()
func executePac(script: String, for url: URL) -> [[CFString: AnyObject]]? {
var error: Unmanaged<CFError>?
let proxiesCopy = CFNetworkCopyProxiesForAutoConfigurationScript(script as CFString, url as CFURL, &error)
guard error == nil,
let cfProxies = proxiesCopy?.takeRetainedValue(),
let proxies = cfProxies as? [CFDictionary]
let proxies = cfProxies as? [[CFString: AnyObject]]
else {
return
}
resolveProxies(from: proxies, for: url) { proxies in
return nil
}
return proxies
}
// MARK: - Private Helper Methods
// MARK: - Helper Methods
private func urlWithNormalizedSheme(from url: URL) -> URL? {
func urlWithNormalizedSheme(from url: URL) -> URL? {
guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true),
let scheme = urlComponents.scheme
else {
@ -243,7 +252,7 @@ public class ProxyResolver {
return urlComponents.url
}
fileprivate class func getCredentials(for host: String) -> Credentials? {
class func getCredentials(for host: String) -> Credentials? {
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: host,
kSecMatchLimit as String: kSecMatchLimitOne,