SessionDelegate.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. //
  2. // SessionDelegate.swift
  3. //
  4. // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Foundation
  25. /// Class which implements the various `URLSessionDelegate` methods to connect various Alamofire features.
  26. open class SessionDelegate: NSObject {
  27. private let fileManager: FileManager
  28. weak var stateProvider: SessionStateProvider?
  29. var eventMonitor: EventMonitor?
  30. /// Creates an instance from the given `FileManager`.
  31. ///
  32. /// - Parameter fileManager: `FileManager` to use for underlying file management, such as moving downloaded files.
  33. /// `.default` by default.
  34. public init(fileManager: FileManager = .default) {
  35. self.fileManager = fileManager
  36. }
  37. /// Internal method to find and cast requests while maintaining some integrity checking.
  38. ///
  39. /// - Parameters:
  40. /// - task: The `URLSessionTask` for which to find the associated `Request`.
  41. /// - type: The `Request` subclass type to cast any `Request` associate with `task`.
  42. func request<R: Request>(for task: URLSessionTask, as type: R.Type) -> R? {
  43. guard let provider = stateProvider else {
  44. assertionFailure("StateProvider is nil.")
  45. return nil
  46. }
  47. return provider.request(for: task) as? R
  48. }
  49. }
  50. /// Type which provides various `Session` state values.
  51. protocol SessionStateProvider: AnyObject {
  52. var serverTrustManager: ServerTrustManager? { get }
  53. var redirectHandler: RedirectHandler? { get }
  54. var cachedResponseHandler: CachedResponseHandler? { get }
  55. func request(for task: URLSessionTask) -> Request?
  56. func didGatherMetricsForTask(_ task: URLSessionTask)
  57. func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void)
  58. func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential?
  59. func cancelRequestsForSessionInvalidation(with error: Error?)
  60. }
  61. // MARK: URLSessionDelegate
  62. extension SessionDelegate: URLSessionDelegate {
  63. open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
  64. eventMonitor?.urlSession(session, didBecomeInvalidWithError: error)
  65. stateProvider?.cancelRequestsForSessionInvalidation(with: error)
  66. }
  67. }
  68. // MARK: URLSessionTaskDelegate
  69. extension SessionDelegate: URLSessionTaskDelegate {
  70. /// Result of a `URLAuthenticationChallenge` evaluation.
  71. typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: AFError?)
  72. open func urlSession(_ session: URLSession,
  73. task: URLSessionTask,
  74. didReceive challenge: URLAuthenticationChallenge,
  75. completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  76. eventMonitor?.urlSession(session, task: task, didReceive: challenge)
  77. let evaluation: ChallengeEvaluation
  78. switch challenge.protectionSpace.authenticationMethod {
  79. case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM,
  80. NSURLAuthenticationMethodNegotiate:
  81. evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)
  82. #if canImport(Security)
  83. case NSURLAuthenticationMethodServerTrust:
  84. evaluation = attemptServerTrustAuthentication(with: challenge)
  85. case NSURLAuthenticationMethodClientCertificate:
  86. evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)
  87. #endif
  88. default:
  89. evaluation = (.performDefaultHandling, nil, nil)
  90. }
  91. if let error = evaluation.error {
  92. stateProvider?.request(for: task)?.didFailTask(task, earlyWithError: error)
  93. }
  94. completionHandler(evaluation.disposition, evaluation.credential)
  95. }
  96. #if canImport(Security)
  97. /// Evaluates the server trust `URLAuthenticationChallenge` received.
  98. ///
  99. /// - Parameter challenge: The `URLAuthenticationChallenge`.
  100. ///
  101. /// - Returns: The `ChallengeEvaluation`.
  102. func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation {
  103. let host = challenge.protectionSpace.host
  104. guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
  105. let trust = challenge.protectionSpace.serverTrust
  106. else {
  107. return (.performDefaultHandling, nil, nil)
  108. }
  109. do {
  110. guard let evaluator = try stateProvider?.serverTrustManager?.serverTrustEvaluator(forHost: host) else {
  111. return (.performDefaultHandling, nil, nil)
  112. }
  113. try evaluator.evaluate(trust, forHost: host)
  114. return (.useCredential, URLCredential(trust: trust), nil)
  115. } catch {
  116. return (.cancelAuthenticationChallenge, nil, error.asAFError(or: .serverTrustEvaluationFailed(reason: .customEvaluationFailed(error: error))))
  117. }
  118. }
  119. #endif
  120. /// Evaluates the credential-based authentication `URLAuthenticationChallenge` received for `task`.
  121. ///
  122. /// - Parameters:
  123. /// - challenge: The `URLAuthenticationChallenge`.
  124. /// - task: The `URLSessionTask` which received the challenge.
  125. ///
  126. /// - Returns: The `ChallengeEvaluation`.
  127. func attemptCredentialAuthentication(for challenge: URLAuthenticationChallenge,
  128. belongingTo task: URLSessionTask) -> ChallengeEvaluation {
  129. guard challenge.previousFailureCount == 0 else {
  130. return (.rejectProtectionSpace, nil, nil)
  131. }
  132. guard let credential = stateProvider?.credential(for: task, in: challenge.protectionSpace) else {
  133. return (.performDefaultHandling, nil, nil)
  134. }
  135. return (.useCredential, credential, nil)
  136. }
  137. open func urlSession(_ session: URLSession,
  138. task: URLSessionTask,
  139. didSendBodyData bytesSent: Int64,
  140. totalBytesSent: Int64,
  141. totalBytesExpectedToSend: Int64) {
  142. eventMonitor?.urlSession(session,
  143. task: task,
  144. didSendBodyData: bytesSent,
  145. totalBytesSent: totalBytesSent,
  146. totalBytesExpectedToSend: totalBytesExpectedToSend)
  147. stateProvider?.request(for: task)?.updateUploadProgress(totalBytesSent: totalBytesSent,
  148. totalBytesExpectedToSend: totalBytesExpectedToSend)
  149. }
  150. open func urlSession(_ session: URLSession,
  151. task: URLSessionTask,
  152. needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) {
  153. eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task)
  154. guard let request = request(for: task, as: UploadRequest.self) else {
  155. assertionFailure("needNewBodyStream did not find UploadRequest.")
  156. completionHandler(nil)
  157. return
  158. }
  159. completionHandler(request.inputStream())
  160. }
  161. open func urlSession(_ session: URLSession,
  162. task: URLSessionTask,
  163. willPerformHTTPRedirection response: HTTPURLResponse,
  164. newRequest request: URLRequest,
  165. completionHandler: @escaping (URLRequest?) -> Void) {
  166. eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request)
  167. if let redirectHandler = stateProvider?.request(for: task)?.redirectHandler ?? stateProvider?.redirectHandler {
  168. redirectHandler.task(task, willBeRedirectedTo: request, for: response, completion: completionHandler)
  169. } else {
  170. completionHandler(request)
  171. }
  172. }
  173. open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
  174. eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics)
  175. stateProvider?.request(for: task)?.didGatherMetrics(metrics)
  176. stateProvider?.didGatherMetricsForTask(task)
  177. }
  178. open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  179. eventMonitor?.urlSession(session, task: task, didCompleteWithError: error)
  180. let request = stateProvider?.request(for: task)
  181. stateProvider?.didCompleteTask(task) {
  182. request?.didCompleteTask(task, with: error.map { $0.asAFError(or: .sessionTaskFailed(error: $0)) })
  183. }
  184. }
  185. @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
  186. open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
  187. eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task)
  188. }
  189. }
  190. // MARK: URLSessionDataDelegate
  191. extension SessionDelegate: URLSessionDataDelegate {
  192. open func urlSession(_ session: URLSession,
  193. dataTask: URLSessionDataTask,
  194. didReceive response: URLResponse,
  195. completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
  196. eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: response)
  197. guard let response = response as? HTTPURLResponse else { completionHandler(.allow); return }
  198. if let request = request(for: dataTask, as: DataRequest.self) {
  199. request.didReceiveResponse(response, completionHandler: completionHandler)
  200. } else if let request = request(for: dataTask, as: DataStreamRequest.self) {
  201. request.didReceiveResponse(response, completionHandler: completionHandler)
  202. } else {
  203. assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive response")
  204. completionHandler(.allow)
  205. return
  206. }
  207. }
  208. open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
  209. eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)
  210. if let request = request(for: dataTask, as: DataRequest.self) {
  211. request.didReceive(data: data)
  212. } else if let request = request(for: dataTask, as: DataStreamRequest.self) {
  213. request.didReceive(data: data)
  214. } else {
  215. assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive data")
  216. return
  217. }
  218. }
  219. open func urlSession(_ session: URLSession,
  220. dataTask: URLSessionDataTask,
  221. willCacheResponse proposedResponse: CachedURLResponse,
  222. completionHandler: @escaping (CachedURLResponse?) -> Void) {
  223. eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)
  224. if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler {
  225. handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler)
  226. } else {
  227. completionHandler(proposedResponse)
  228. }
  229. }
  230. }
  231. // MARK: URLSessionDownloadDelegate
  232. extension SessionDelegate: URLSessionDownloadDelegate {
  233. open func urlSession(_ session: URLSession,
  234. downloadTask: URLSessionDownloadTask,
  235. didResumeAtOffset fileOffset: Int64,
  236. expectedTotalBytes: Int64) {
  237. eventMonitor?.urlSession(session,
  238. downloadTask: downloadTask,
  239. didResumeAtOffset: fileOffset,
  240. expectedTotalBytes: expectedTotalBytes)
  241. guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else {
  242. assertionFailure("downloadTask did not find DownloadRequest.")
  243. return
  244. }
  245. downloadRequest.updateDownloadProgress(bytesWritten: fileOffset,
  246. totalBytesExpectedToWrite: expectedTotalBytes)
  247. }
  248. open func urlSession(_ session: URLSession,
  249. downloadTask: URLSessionDownloadTask,
  250. didWriteData bytesWritten: Int64,
  251. totalBytesWritten: Int64,
  252. totalBytesExpectedToWrite: Int64) {
  253. eventMonitor?.urlSession(session,
  254. downloadTask: downloadTask,
  255. didWriteData: bytesWritten,
  256. totalBytesWritten: totalBytesWritten,
  257. totalBytesExpectedToWrite: totalBytesExpectedToWrite)
  258. guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else {
  259. assertionFailure("downloadTask did not find DownloadRequest.")
  260. return
  261. }
  262. downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten,
  263. totalBytesExpectedToWrite: totalBytesExpectedToWrite)
  264. }
  265. open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
  266. eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
  267. guard let request = request(for: downloadTask, as: DownloadRequest.self) else {
  268. assertionFailure("downloadTask did not find DownloadRequest.")
  269. return
  270. }
  271. let (destination, options): (URL, DownloadRequest.Options)
  272. if let response = request.response {
  273. (destination, options) = request.destination(location, response)
  274. } else {
  275. // If there's no response this is likely a local file download, so generate the temporary URL directly.
  276. (destination, options) = (DownloadRequest.defaultDestinationURL(location), [])
  277. }
  278. eventMonitor?.request(request, didCreateDestinationURL: destination)
  279. do {
  280. if options.contains(.removePreviousFile), fileManager.fileExists(atPath: destination.path) {
  281. try fileManager.removeItem(at: destination)
  282. }
  283. if options.contains(.createIntermediateDirectories) {
  284. let directory = destination.deletingLastPathComponent()
  285. try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
  286. }
  287. try fileManager.moveItem(at: location, to: destination)
  288. request.didFinishDownloading(using: downloadTask, with: .success(destination))
  289. } catch {
  290. request.didFinishDownloading(using: downloadTask, with: .failure(.downloadedFileMoveFailed(error: error,
  291. source: location,
  292. destination: destination)))
  293. }
  294. }
  295. }