110 lines
3.5 KiB
Swift
110 lines
3.5 KiB
Swift
import Foundation
|
|
#if canImport(FoundationNetworking)
|
|
import FoundationNetworking
|
|
#endif
|
|
|
|
enum LocalhostTrustPolicy {
|
|
static func shouldAcceptServerTrust(
|
|
host: String,
|
|
authenticationMethod: String,
|
|
hasServerTrust: Bool) -> Bool
|
|
{
|
|
#if !os(Linux)
|
|
guard authenticationMethod == NSURLAuthenticationMethodServerTrust else { return false }
|
|
#endif
|
|
let normalizedHost = host.lowercased()
|
|
guard normalizedHost == "127.0.0.1" || normalizedHost == "localhost" else { return false }
|
|
return hasServerTrust
|
|
}
|
|
}
|
|
|
|
final class LocalhostSessionDelegate: NSObject {
|
|
func data(for request: URLRequest, session: URLSession) async throws -> (Data, URLResponse) {
|
|
let state = LocalhostSessionTaskState()
|
|
return try await withTaskCancellationHandler {
|
|
try await withCheckedThrowingContinuation { continuation in
|
|
let task = session.dataTask(with: request) { data, response, error in
|
|
if let error {
|
|
continuation.resume(throwing: error)
|
|
return
|
|
}
|
|
guard let data, let response else {
|
|
continuation.resume(throwing: AntigravityStatusProbeError.apiError("Invalid response"))
|
|
return
|
|
}
|
|
continuation.resume(returning: (data, response))
|
|
}
|
|
state.setTask(task)
|
|
task.resume()
|
|
}
|
|
} onCancel: {
|
|
state.cancel()
|
|
}
|
|
}
|
|
|
|
private func challengeResult(_ challenge: URLAuthenticationChallenge) -> (
|
|
disposition: URLSession.AuthChallengeDisposition,
|
|
credential: URLCredential?)
|
|
{
|
|
#if os(Linux)
|
|
return (.performDefaultHandling, nil)
|
|
#else
|
|
let protectionSpace = challenge.protectionSpace
|
|
let trust = protectionSpace.serverTrust
|
|
guard LocalhostTrustPolicy.shouldAcceptServerTrust(
|
|
host: protectionSpace.host,
|
|
authenticationMethod: protectionSpace.authenticationMethod,
|
|
hasServerTrust: trust != nil),
|
|
let trust
|
|
else {
|
|
return (.performDefaultHandling, nil)
|
|
}
|
|
return (.useCredential, URLCredential(trust: trust))
|
|
#endif
|
|
}
|
|
}
|
|
|
|
extension LocalhostSessionDelegate: URLSessionDelegate {
|
|
func urlSession(
|
|
_ session: URLSession,
|
|
didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?)
|
|
{
|
|
self.challengeResult(challenge)
|
|
}
|
|
}
|
|
|
|
extension LocalhostSessionDelegate: URLSessionTaskDelegate {
|
|
func urlSession(
|
|
_ session: URLSession,
|
|
task: URLSessionTask,
|
|
didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?)
|
|
{
|
|
self.challengeResult(challenge)
|
|
}
|
|
}
|
|
|
|
private final class LocalhostSessionTaskState: @unchecked Sendable {
|
|
private let lock = NSLock()
|
|
private var task: URLSessionDataTask?
|
|
private var isCancelled = false
|
|
|
|
func setTask(_ task: URLSessionDataTask) {
|
|
self.lock.lock()
|
|
self.task = task
|
|
let shouldCancel = self.isCancelled
|
|
self.lock.unlock()
|
|
|
|
if shouldCancel {
|
|
task.cancel()
|
|
}
|
|
}
|
|
|
|
func cancel() {
|
|
self.lock.lock()
|
|
self.isCancelled = true
|
|
let task = self.task
|
|
self.lock.unlock()
|
|
task?.cancel()
|
|
}
|
|
}
|