SessionDelegate.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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. }
  38. /// Type which provides various `Session` state values.
  39. protocol SessionStateProvider: AnyObject {
  40. var serverTrustManager: ServerTrustManager? { get }
  41. var redirectHandler: RedirectHandler? { get }
  42. var cachedResponseHandler: CachedResponseHandler? { get }
  43. func request(for task: URLSessionTask) -> Request?
  44. func didGatherMetricsForTask(_ task: URLSessionTask)
  45. func didCompleteTask(_ task: URLSessionTask)
  46. func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential?
  47. func cancelRequestsForSessionInvalidation(with error: Error?)
  48. }
  49. // MARK: URLSessionDelegate
  50. extension SessionDelegate: URLSessionDelegate {
  51. open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
  52. eventMonitor?.urlSession(session, didBecomeInvalidWithError: error)
  53. stateProvider?.cancelRequestsForSessionInvalidation(with: error)
  54. }
  55. }
  56. // MARK: URLSessionTaskDelegate
  57. extension SessionDelegate: URLSessionTaskDelegate {
  58. /// Result of a `URLAuthenticationChallenge` evaluation.
  59. typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: AFError?)
  60. open func urlSession(_ session: URLSession,
  61. task: URLSessionTask,
  62. didReceive challenge: URLAuthenticationChallenge,
  63. completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  64. eventMonitor?.urlSession(session, task: task, didReceive: challenge)
  65. let evaluation: ChallengeEvaluation
  66. switch challenge.protectionSpace.authenticationMethod {
  67. case NSURLAuthenticationMethodServerTrust:
  68. evaluation = attemptServerTrustAuthentication(with: challenge)
  69. case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate, NSURLAuthenticationMethodClientCertificate:
  70. evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)
  71. default:
  72. evaluation = (.performDefaultHandling, nil, nil)
  73. }
  74. if let error = evaluation.error {
  75. stateProvider?.request(for: task)?.didFailTask(task, earlyWithError: error)
  76. }
  77. completionHandler(evaluation.disposition, evaluation.credential)
  78. }
  79. /// Evaluates the server trust `URLAuthenticationChallenge` received.
  80. ///
  81. /// - Parameter challenge: The `URLAuthenticationChallenge`.
  82. ///
  83. /// - Returns: The `ChallengeEvaluation`.
  84. func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation {
  85. let host = challenge.protectionSpace.host
  86. guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
  87. let trust = challenge.protectionSpace.serverTrust
  88. else {
  89. return (.performDefaultHandling, nil, nil)
  90. }
  91. do {
  92. guard let evaluator = try stateProvider?.serverTrustManager?.serverTrustEvaluator(forHost: host) else {
  93. return (.performDefaultHandling, nil, nil)
  94. }
  95. try evaluator.evaluate(trust, forHost: host)
  96. return (.useCredential, URLCredential(trust: trust), nil)
  97. } catch {
  98. return (.cancelAuthenticationChallenge, nil, error.asAFError(or: .serverTrustEvaluationFailed(reason: .customEvaluationFailed(error: error))))
  99. }
  100. }
  101. /// Evaluates the credential-based authentication `URLAuthenticationChallenge` received for `task`.
  102. ///
  103. /// - Parameters:
  104. /// - challenge: The `URLAuthenticationChallenge`.
  105. /// - task: The `URLSessionTask` which received the challenge.
  106. ///
  107. /// - Returns: The `ChallengeEvaluation`.
  108. func attemptCredentialAuthentication(for challenge: URLAuthenticationChallenge,
  109. belongingTo task: URLSessionTask) -> ChallengeEvaluation {
  110. guard challenge.previousFailureCount == 0 else {
  111. return (.rejectProtectionSpace, nil, nil)
  112. }
  113. guard let credential = stateProvider?.credential(for: task, in: challenge.protectionSpace) else {
  114. return (.performDefaultHandling, nil, nil)
  115. }
  116. return (.useCredential, credential, nil)
  117. }
  118. open func urlSession(_ session: URLSession,
  119. task: URLSessionTask,
  120. didSendBodyData bytesSent: Int64,
  121. totalBytesSent: Int64,
  122. totalBytesExpectedToSend: Int64) {
  123. eventMonitor?.urlSession(session,
  124. task: task,
  125. didSendBodyData: bytesSent,
  126. totalBytesSent: totalBytesSent,
  127. totalBytesExpectedToSend: totalBytesExpectedToSend)
  128. stateProvider?.request(for: task)?.updateUploadProgress(totalBytesSent: totalBytesSent,
  129. totalBytesExpectedToSend: totalBytesExpectedToSend)
  130. }
  131. open func urlSession(_ session: URLSession,
  132. task: URLSessionTask,
  133. needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) {
  134. eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task)
  135. guard let request = stateProvider?.request(for: task) as? UploadRequest else {
  136. fatalError("needNewBodyStream for request that isn't UploadRequest.")
  137. }
  138. completionHandler(request.inputStream())
  139. }
  140. open func urlSession(_ session: URLSession,
  141. task: URLSessionTask,
  142. willPerformHTTPRedirection response: HTTPURLResponse,
  143. newRequest request: URLRequest,
  144. completionHandler: @escaping (URLRequest?) -> Void) {
  145. eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request)
  146. if let redirectHandler = stateProvider?.request(for: task)?.redirectHandler ?? stateProvider?.redirectHandler {
  147. redirectHandler.task(task, willBeRedirectedTo: request, for: response, completion: completionHandler)
  148. } else {
  149. completionHandler(request)
  150. }
  151. }
  152. open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
  153. eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics)
  154. stateProvider?.request(for: task)?.didGatherMetrics(metrics)
  155. stateProvider?.didGatherMetricsForTask(task)
  156. }
  157. open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  158. eventMonitor?.urlSession(session, task: task, didCompleteWithError: error)
  159. stateProvider?.request(for: task)?.didCompleteTask(task, with: error.map { $0.asAFError(or: .sessionTaskFailed(error: $0)) })
  160. stateProvider?.didCompleteTask(task)
  161. }
  162. @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
  163. open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
  164. eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task)
  165. }
  166. }
  167. // MARK: URLSessionDataDelegate
  168. extension SessionDelegate: URLSessionDataDelegate {
  169. open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
  170. eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)
  171. guard let request = stateProvider?.request(for: dataTask) as? DataRequest else {
  172. fatalError("dataTask received data for incorrect Request subclass: \(String(describing: stateProvider?.request(for: dataTask)))")
  173. }
  174. request.didReceive(data: data)
  175. }
  176. open func urlSession(_ session: URLSession,
  177. dataTask: URLSessionDataTask,
  178. willCacheResponse proposedResponse: CachedURLResponse,
  179. completionHandler: @escaping (CachedURLResponse?) -> Void) {
  180. eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)
  181. if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler {
  182. handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler)
  183. } else {
  184. completionHandler(proposedResponse)
  185. }
  186. }
  187. }
  188. // MARK: URLSessionDownloadDelegate
  189. extension SessionDelegate: URLSessionDownloadDelegate {
  190. open func urlSession(_ session: URLSession,
  191. downloadTask: URLSessionDownloadTask,
  192. didResumeAtOffset fileOffset: Int64,
  193. expectedTotalBytes: Int64) {
  194. eventMonitor?.urlSession(session,
  195. downloadTask: downloadTask,
  196. didResumeAtOffset: fileOffset,
  197. expectedTotalBytes: expectedTotalBytes)
  198. guard let downloadRequest = stateProvider?.request(for: downloadTask) as? DownloadRequest else {
  199. fatalError("No DownloadRequest found for downloadTask: \(downloadTask)")
  200. }
  201. downloadRequest.updateDownloadProgress(bytesWritten: fileOffset,
  202. totalBytesExpectedToWrite: expectedTotalBytes)
  203. }
  204. open func urlSession(_ session: URLSession,
  205. downloadTask: URLSessionDownloadTask,
  206. didWriteData bytesWritten: Int64,
  207. totalBytesWritten: Int64,
  208. totalBytesExpectedToWrite: Int64) {
  209. eventMonitor?.urlSession(session,
  210. downloadTask: downloadTask,
  211. didWriteData: bytesWritten,
  212. totalBytesWritten: totalBytesWritten,
  213. totalBytesExpectedToWrite: totalBytesExpectedToWrite)
  214. guard let downloadRequest = stateProvider?.request(for: downloadTask) as? DownloadRequest else {
  215. fatalError("No DownloadRequest found for downloadTask: \(downloadTask)")
  216. }
  217. downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten,
  218. totalBytesExpectedToWrite: totalBytesExpectedToWrite)
  219. }
  220. open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
  221. eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
  222. guard let request = stateProvider?.request(for: downloadTask) as? DownloadRequest else {
  223. fatalError("Download finished but either no request found or request wasn't DownloadRequest")
  224. }
  225. guard let response = request.response else {
  226. fatalError("URLSessionDownloadTask finished downloading with no response.")
  227. }
  228. let (destination, options) = (request.destination)(location, response)
  229. eventMonitor?.request(request, didCreateDestinationURL: destination)
  230. do {
  231. if options.contains(.removePreviousFile), fileManager.fileExists(atPath: destination.path) {
  232. try fileManager.removeItem(at: destination)
  233. }
  234. if options.contains(.createIntermediateDirectories) {
  235. let directory = destination.deletingLastPathComponent()
  236. try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
  237. }
  238. try fileManager.moveItem(at: location, to: destination)
  239. request.didFinishDownloading(using: downloadTask, with: .success(destination))
  240. } catch {
  241. request.didFinishDownloading(using: downloadTask, with: .failure(.downloadedFileMoveFailed(error: error, source: location, destination: destination)))
  242. }
  243. }
  244. }