Working on PAC support
This commit is contained in:
parent
951d206600
commit
442ae5ce94
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue