When providing a URL for a PAC file in the HTTP Proxy settings for a WiFi network, is it possible to get the returned value after the PAC file is parsed?
I think you mean executed, not parsed, right? If so, you should take a look at
CFNetworkExecuteProxyAutoConfigurationScript
and
CFNetworkExecuteProxyAutoConfigurationURL
, which can run PAC scripts with a target URL and return you the result.
Calling those from Swift is ‘fun’. There’s a bunch of complexities here, including:
It’s async, so you have to deal with mapping a Swift object to a CF
info
pointer and back.It’s run loop based, so you have target a specific thread (in my code I always target the main thread).
The API itself requires that you serialise requests.
The code pasted in below should get you started.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
import Foundation
class PACResolver {
init(script: String) {
self.script = script
}
let script: String
enum Result {
case error(error: CFError)
case proxies(proxies: CFArray)
}
typealias Callback = (_ result: Result) -> Void
private struct Request {
let targetURL: URL
let callback: Callback
}
private var requests: [Request] = []
private var runLoopSource: CFRunLoopSource?
func resolve(targetURL: URL, callback: @escaping Callback) {
DispatchQueue.main.async {
let wasEmpty = self.requests.isEmpty
self.requests.append(Request(targetURL: targetURL, callback: callback))
if wasEmpty {
self.startNextRequest()
}
}
}
private func startNextRequest() {
guard let request = self.requests.first else {
return
}
var context = CFStreamClientContext()
context.info = Unmanaged.passRetained(self).toOpaque()
let rls = CFNetworkExecuteProxyAutoConfigurationScript(
self.script as CFString,
request.targetURL as CFURL,
{ (info, proxies, error) in
let obj = Unmanaged<PACResolver>.fromOpaque(info).takeRetainedValue()
if let error = error {
obj.resolveDidFinish(result: .error(error: error))
} else {
obj.resolveDidFinish(result: .proxies(proxies: proxies))
}
},
&context
).takeUnretainedValue()
assert(self.runLoopSource == nil)
self.runLoopSource = rls
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, .defaultMode)
}
private func resolveDidFinish(result: Result) {
CFRunLoopSourceInvalidate(self.runLoopSource!)
self.runLoopSource = nil
let request = self.requests.removeFirst()
request.callback(result)
self.startNextRequest()
}
}