SessionStateProvider.swift 12 KB

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