123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- //
- // SessionDelegate.swift
- //
- // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- import Foundation
- /// Class which implements the various `URLSessionDelegate` methods to connect various Alamofire features.
- open class SessionDelegate: NSObject {
- private let fileManager: FileManager
- weak var stateProvider: SessionStateProvider?
- var eventMonitor: EventMonitor?
- /// Creates an instance from the given `FileManager`.
- ///
- /// - Parameter fileManager: `FileManager` to use for underlying file management, such as moving downloaded files.
- /// `.default` by default.
- public init(fileManager: FileManager = .default) {
- self.fileManager = fileManager
- }
- /// Internal method to find and cast requests while maintaining some integrity checking.
- ///
- /// - Parameters:
- /// - task: The `URLSessionTask` for which to find the associated `Request`.
- /// - type: The `Request` subclass type to cast any `Request` associate with `task`.
- func request<R: Request>(for task: URLSessionTask, as type: R.Type) -> R? {
- guard let provider = stateProvider else {
- assertionFailure("StateProvider is nil.")
- return nil
- }
- return provider.request(for: task) as? R
- }
- }
- /// Type which provides various `Session` state values.
- protocol SessionStateProvider: AnyObject {
- var serverTrustManager: ServerTrustManager? { get }
- var redirectHandler: RedirectHandler? { get }
- var cachedResponseHandler: CachedResponseHandler? { get }
- func request(for task: URLSessionTask) -> Request?
- func didGatherMetricsForTask(_ task: URLSessionTask)
- func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void)
- func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential?
- func cancelRequestsForSessionInvalidation(with error: Error?)
- }
- // MARK: URLSessionDelegate
- extension SessionDelegate: URLSessionDelegate {
- open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
- eventMonitor?.urlSession(session, didBecomeInvalidWithError: error)
- stateProvider?.cancelRequestsForSessionInvalidation(with: error)
- }
- }
- // MARK: URLSessionTaskDelegate
- extension SessionDelegate: URLSessionTaskDelegate {
- /// Result of a `URLAuthenticationChallenge` evaluation.
- typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: AFError?)
- open func urlSession(_ session: URLSession,
- task: URLSessionTask,
- didReceive challenge: URLAuthenticationChallenge,
- completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
- eventMonitor?.urlSession(session, task: task, didReceive: challenge)
- let evaluation: ChallengeEvaluation
- switch challenge.protectionSpace.authenticationMethod {
- case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM,
- NSURLAuthenticationMethodNegotiate:
- evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)
- #if canImport(Security)
- case NSURLAuthenticationMethodServerTrust:
- evaluation = attemptServerTrustAuthentication(with: challenge)
- case NSURLAuthenticationMethodClientCertificate:
- evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)
- #endif
- default:
- evaluation = (.performDefaultHandling, nil, nil)
- }
- if let error = evaluation.error {
- stateProvider?.request(for: task)?.didFailTask(task, earlyWithError: error)
- }
- completionHandler(evaluation.disposition, evaluation.credential)
- }
- #if canImport(Security)
- /// Evaluates the server trust `URLAuthenticationChallenge` received.
- ///
- /// - Parameter challenge: The `URLAuthenticationChallenge`.
- ///
- /// - Returns: The `ChallengeEvaluation`.
- func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation {
- let host = challenge.protectionSpace.host
- guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
- let trust = challenge.protectionSpace.serverTrust
- else {
- return (.performDefaultHandling, nil, nil)
- }
- do {
- guard let evaluator = try stateProvider?.serverTrustManager?.serverTrustEvaluator(forHost: host) else {
- return (.performDefaultHandling, nil, nil)
- }
- try evaluator.evaluate(trust, forHost: host)
- return (.useCredential, URLCredential(trust: trust), nil)
- } catch {
- return (.cancelAuthenticationChallenge, nil, error.asAFError(or: .serverTrustEvaluationFailed(reason: .customEvaluationFailed(error: error))))
- }
- }
- #endif
- /// Evaluates the credential-based authentication `URLAuthenticationChallenge` received for `task`.
- ///
- /// - Parameters:
- /// - challenge: The `URLAuthenticationChallenge`.
- /// - task: The `URLSessionTask` which received the challenge.
- ///
- /// - Returns: The `ChallengeEvaluation`.
- func attemptCredentialAuthentication(for challenge: URLAuthenticationChallenge,
- belongingTo task: URLSessionTask) -> ChallengeEvaluation {
- guard challenge.previousFailureCount == 0 else {
- return (.rejectProtectionSpace, nil, nil)
- }
- guard let credential = stateProvider?.credential(for: task, in: challenge.protectionSpace) else {
- return (.performDefaultHandling, nil, nil)
- }
- return (.useCredential, credential, nil)
- }
- open func urlSession(_ session: URLSession,
- task: URLSessionTask,
- didSendBodyData bytesSent: Int64,
- totalBytesSent: Int64,
- totalBytesExpectedToSend: Int64) {
- eventMonitor?.urlSession(session,
- task: task,
- didSendBodyData: bytesSent,
- totalBytesSent: totalBytesSent,
- totalBytesExpectedToSend: totalBytesExpectedToSend)
- stateProvider?.request(for: task)?.updateUploadProgress(totalBytesSent: totalBytesSent,
- totalBytesExpectedToSend: totalBytesExpectedToSend)
- }
- open func urlSession(_ session: URLSession,
- task: URLSessionTask,
- needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) {
- eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task)
- guard let request = request(for: task, as: UploadRequest.self) else {
- assertionFailure("needNewBodyStream did not find UploadRequest.")
- completionHandler(nil)
- return
- }
- completionHandler(request.inputStream())
- }
- open func urlSession(_ session: URLSession,
- task: URLSessionTask,
- willPerformHTTPRedirection response: HTTPURLResponse,
- newRequest request: URLRequest,
- completionHandler: @escaping (URLRequest?) -> Void) {
- eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request)
- if let redirectHandler = stateProvider?.request(for: task)?.redirectHandler ?? stateProvider?.redirectHandler {
- redirectHandler.task(task, willBeRedirectedTo: request, for: response, completion: completionHandler)
- } else {
- completionHandler(request)
- }
- }
- open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
- eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics)
- stateProvider?.request(for: task)?.didGatherMetrics(metrics)
- stateProvider?.didGatherMetricsForTask(task)
- }
- open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
- eventMonitor?.urlSession(session, task: task, didCompleteWithError: error)
- let request = stateProvider?.request(for: task)
- stateProvider?.didCompleteTask(task) {
- request?.didCompleteTask(task, with: error.map { $0.asAFError(or: .sessionTaskFailed(error: $0)) })
- }
- }
- @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
- open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
- eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task)
- }
- }
- // MARK: URLSessionDataDelegate
- extension SessionDelegate: URLSessionDataDelegate {
- open func urlSession(_ session: URLSession,
- dataTask: URLSessionDataTask,
- didReceive response: URLResponse,
- completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
- eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: response)
- guard let response = response as? HTTPURLResponse else { completionHandler(.allow); return }
- if let request = request(for: dataTask, as: DataRequest.self) {
- request.didReceiveResponse(response, completionHandler: completionHandler)
- } else if let request = request(for: dataTask, as: DataStreamRequest.self) {
- request.didReceiveResponse(response, completionHandler: completionHandler)
- } else {
- assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive response")
- completionHandler(.allow)
- return
- }
- }
- open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
- eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)
- if let request = request(for: dataTask, as: DataRequest.self) {
- request.didReceive(data: data)
- } else if let request = request(for: dataTask, as: DataStreamRequest.self) {
- request.didReceive(data: data)
- } else {
- assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive data")
- return
- }
- }
- open func urlSession(_ session: URLSession,
- dataTask: URLSessionDataTask,
- willCacheResponse proposedResponse: CachedURLResponse,
- completionHandler: @escaping (CachedURLResponse?) -> Void) {
- eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)
- if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler {
- handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler)
- } else {
- completionHandler(proposedResponse)
- }
- }
- }
- // MARK: URLSessionDownloadDelegate
- extension SessionDelegate: URLSessionDownloadDelegate {
- open func urlSession(_ session: URLSession,
- downloadTask: URLSessionDownloadTask,
- didResumeAtOffset fileOffset: Int64,
- expectedTotalBytes: Int64) {
- eventMonitor?.urlSession(session,
- downloadTask: downloadTask,
- didResumeAtOffset: fileOffset,
- expectedTotalBytes: expectedTotalBytes)
- guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else {
- assertionFailure("downloadTask did not find DownloadRequest.")
- return
- }
- downloadRequest.updateDownloadProgress(bytesWritten: fileOffset,
- totalBytesExpectedToWrite: expectedTotalBytes)
- }
- open func urlSession(_ session: URLSession,
- downloadTask: URLSessionDownloadTask,
- didWriteData bytesWritten: Int64,
- totalBytesWritten: Int64,
- totalBytesExpectedToWrite: Int64) {
- eventMonitor?.urlSession(session,
- downloadTask: downloadTask,
- didWriteData: bytesWritten,
- totalBytesWritten: totalBytesWritten,
- totalBytesExpectedToWrite: totalBytesExpectedToWrite)
- guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else {
- assertionFailure("downloadTask did not find DownloadRequest.")
- return
- }
- downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten,
- totalBytesExpectedToWrite: totalBytesExpectedToWrite)
- }
- open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
- eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
- guard let request = request(for: downloadTask, as: DownloadRequest.self) else {
- assertionFailure("downloadTask did not find DownloadRequest.")
- return
- }
- let (destination, options): (URL, DownloadRequest.Options)
- if let response = request.response {
- (destination, options) = request.destination(location, response)
- } else {
- // If there's no response this is likely a local file download, so generate the temporary URL directly.
- (destination, options) = (DownloadRequest.defaultDestinationURL(location), [])
- }
- eventMonitor?.request(request, didCreateDestinationURL: destination)
- do {
- if options.contains(.removePreviousFile), fileManager.fileExists(atPath: destination.path) {
- try fileManager.removeItem(at: destination)
- }
- if options.contains(.createIntermediateDirectories) {
- let directory = destination.deletingLastPathComponent()
- try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
- }
- try fileManager.moveItem(at: location, to: destination)
- request.didFinishDownloading(using: downloadTask, with: .success(destination))
- } catch {
- request.didFinishDownloading(using: downloadTask, with: .failure(.downloadedFileMoveFailed(error: error,
- source: location,
- destination: destination)))
- }
- }
- }
|