added iOS source code
[wl-app.git] / iOS / Pods / Alamofire / Source / Request.swift
1 //
2 //  Request.swift
3 //
4 //  Copyright (c) 2014-2017 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
25 import Foundation
26
27 /// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
28 public protocol RequestAdapter {
29     /// Inspects and adapts the specified `URLRequest` in some manner if necessary and returns the result.
30     ///
31     /// - parameter urlRequest: The URL request to adapt.
32     ///
33     /// - throws: An `Error` if the adaptation encounters an error.
34     ///
35     /// - returns: The adapted `URLRequest`.
36     func adapt(_ urlRequest: URLRequest) throws -> URLRequest
37 }
38
39 // MARK: -
40
41 /// A closure executed when the `RequestRetrier` determines whether a `Request` should be retried or not.
42 public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
43
44 /// A type that determines whether a request should be retried after being executed by the specified session manager
45 /// and encountering an error.
46 public protocol RequestRetrier {
47     /// Determines whether the `Request` should be retried by calling the `completion` closure.
48     ///
49     /// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs
50     /// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
51     /// cleaned up after.
52     ///
53     /// - parameter manager:    The session manager the request was executed on.
54     /// - parameter request:    The request that failed due to the encountered error.
55     /// - parameter error:      The error encountered when executing the request.
56     /// - parameter completion: The completion closure to be executed when retry decision has been determined.
57     func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
58 }
59
60 // MARK: -
61
62 protocol TaskConvertible {
63     func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask
64 }
65
66 /// A dictionary of headers to apply to a `URLRequest`.
67 public typealias HTTPHeaders = [String: String]
68
69 // MARK: -
70
71 /// Responsible for sending a request and receiving the response and associated data from the server, as well as
72 /// managing its underlying `URLSessionTask`.
73 open class Request {
74
75     // MARK: Helper Types
76
77     /// A closure executed when monitoring upload or download progress of a request.
78     public typealias ProgressHandler = (Progress) -> Void
79
80     enum RequestTask {
81         case data(TaskConvertible?, URLSessionTask?)
82         case download(TaskConvertible?, URLSessionTask?)
83         case upload(TaskConvertible?, URLSessionTask?)
84         case stream(TaskConvertible?, URLSessionTask?)
85     }
86
87     // MARK: Properties
88
89     /// The delegate for the underlying task.
90     open internal(set) var delegate: TaskDelegate {
91         get {
92             taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
93             return taskDelegate
94         }
95         set {
96             taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
97             taskDelegate = newValue
98         }
99     }
100
101     /// The underlying task.
102     open var task: URLSessionTask? { return delegate.task }
103
104     /// The session belonging to the underlying task.
105     open let session: URLSession
106
107     /// The request sent or to be sent to the server.
108     open var request: URLRequest? { return task?.originalRequest }
109
110     /// The response received from the server, if any.
111     open var response: HTTPURLResponse? { return task?.response as? HTTPURLResponse }
112
113     /// The number of times the request has been retried.
114     open internal(set) var retryCount: UInt = 0
115
116     let originalTask: TaskConvertible?
117
118     var startTime: CFAbsoluteTime?
119     var endTime: CFAbsoluteTime?
120
121     var validations: [() -> Void] = []
122
123     private var taskDelegate: TaskDelegate
124     private var taskDelegateLock = NSLock()
125
126     // MARK: Lifecycle
127
128     init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
129         self.session = session
130
131         switch requestTask {
132         case .data(let originalTask, let task):
133             taskDelegate = DataTaskDelegate(task: task)
134             self.originalTask = originalTask
135         case .download(let originalTask, let task):
136             taskDelegate = DownloadTaskDelegate(task: task)
137             self.originalTask = originalTask
138         case .upload(let originalTask, let task):
139             taskDelegate = UploadTaskDelegate(task: task)
140             self.originalTask = originalTask
141         case .stream(let originalTask, let task):
142             taskDelegate = TaskDelegate(task: task)
143             self.originalTask = originalTask
144         }
145
146         delegate.error = error
147         delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
148     }
149
150     // MARK: Authentication
151
152     /// Associates an HTTP Basic credential with the request.
153     ///
154     /// - parameter user:        The user.
155     /// - parameter password:    The password.
156     /// - parameter persistence: The URL credential persistence. `.ForSession` by default.
157     ///
158     /// - returns: The request.
159     @discardableResult
160     open func authenticate(
161         user: String,
162         password: String,
163         persistence: URLCredential.Persistence = .forSession)
164         -> Self
165     {
166         let credential = URLCredential(user: user, password: password, persistence: persistence)
167         return authenticate(usingCredential: credential)
168     }
169
170     /// Associates a specified credential with the request.
171     ///
172     /// - parameter credential: The credential.
173     ///
174     /// - returns: The request.
175     @discardableResult
176     open func authenticate(usingCredential credential: URLCredential) -> Self {
177         delegate.credential = credential
178         return self
179     }
180
181     /// Returns a base64 encoded basic authentication credential as an authorization header tuple.
182     ///
183     /// - parameter user:     The user.
184     /// - parameter password: The password.
185     ///
186     /// - returns: A tuple with Authorization header and credential value if encoding succeeds, `nil` otherwise.
187     open static func authorizationHeader(user: String, password: String) -> (key: String, value: String)? {
188         guard let data = "\(user):\(password)".data(using: .utf8) else { return nil }
189
190         let credential = data.base64EncodedString(options: [])
191
192         return (key: "Authorization", value: "Basic \(credential)")
193     }
194
195     // MARK: State
196
197     /// Resumes the request.
198     open func resume() {
199         guard let task = task else { delegate.queue.isSuspended = false ; return }
200
201         if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
202
203         task.resume()
204
205         NotificationCenter.default.post(
206             name: Notification.Name.Task.DidResume,
207             object: self,
208             userInfo: [Notification.Key.Task: task]
209         )
210     }
211
212     /// Suspends the request.
213     open func suspend() {
214         guard let task = task else { return }
215
216         task.suspend()
217
218         NotificationCenter.default.post(
219             name: Notification.Name.Task.DidSuspend,
220             object: self,
221             userInfo: [Notification.Key.Task: task]
222         )
223     }
224
225     /// Cancels the request.
226     open func cancel() {
227         guard let task = task else { return }
228
229         task.cancel()
230
231         NotificationCenter.default.post(
232             name: Notification.Name.Task.DidCancel,
233             object: self,
234             userInfo: [Notification.Key.Task: task]
235         )
236     }
237 }
238
239 // MARK: - CustomStringConvertible
240
241 extension Request: CustomStringConvertible {
242     /// The textual representation used when written to an output stream, which includes the HTTP method and URL, as
243     /// well as the response status code if a response has been received.
244     open var description: String {
245         var components: [String] = []
246
247         if let HTTPMethod = request?.httpMethod {
248             components.append(HTTPMethod)
249         }
250
251         if let urlString = request?.url?.absoluteString {
252             components.append(urlString)
253         }
254
255         if let response = response {
256             components.append("(\(response.statusCode))")
257         }
258
259         return components.joined(separator: " ")
260     }
261 }
262
263 // MARK: - CustomDebugStringConvertible
264
265 extension Request: CustomDebugStringConvertible {
266     /// The textual representation used when written to an output stream, in the form of a cURL command.
267     open var debugDescription: String {
268         return cURLRepresentation()
269     }
270
271     func cURLRepresentation() -> String {
272         var components = ["$ curl -v"]
273
274         guard let request = self.request,
275               let url = request.url,
276               let host = url.host
277         else {
278             return "$ curl command could not be created"
279         }
280
281         if let httpMethod = request.httpMethod, httpMethod != "GET" {
282             components.append("-X \(httpMethod)")
283         }
284
285         if let credentialStorage = self.session.configuration.urlCredentialStorage {
286             let protectionSpace = URLProtectionSpace(
287                 host: host,
288                 port: url.port ?? 0,
289                 protocol: url.scheme,
290                 realm: host,
291                 authenticationMethod: NSURLAuthenticationMethodHTTPBasic
292             )
293
294             if let credentials = credentialStorage.credentials(for: protectionSpace)?.values {
295                 for credential in credentials {
296                     guard let user = credential.user, let password = credential.password else { continue }
297                     components.append("-u \(user):\(password)")
298                 }
299             } else {
300                 if let credential = delegate.credential, let user = credential.user, let password = credential.password {
301                     components.append("-u \(user):\(password)")
302                 }
303             }
304         }
305
306         if session.configuration.httpShouldSetCookies {
307             if
308                 let cookieStorage = session.configuration.httpCookieStorage,
309                 let cookies = cookieStorage.cookies(for: url), !cookies.isEmpty
310             {
311                 let string = cookies.reduce("") { $0 + "\($1.name)=\($1.value);" }
312
313             #if swift(>=3.2)
314                 components.append("-b \"\(string[..<string.index(before: string.endIndex)])\"")
315             #else
316                 components.append("-b \"\(string.substring(to: string.characters.index(before: string.endIndex)))\"")
317             #endif
318             }
319         }
320
321         var headers: [AnyHashable: Any] = [:]
322
323         if let additionalHeaders = session.configuration.httpAdditionalHeaders {
324             for (field, value) in additionalHeaders where field != AnyHashable("Cookie") {
325                 headers[field] = value
326             }
327         }
328
329         if let headerFields = request.allHTTPHeaderFields {
330             for (field, value) in headerFields where field != "Cookie" {
331                 headers[field] = value
332             }
333         }
334
335         for (field, value) in headers {
336             components.append("-H \"\(field): \(value)\"")
337         }
338
339         if let httpBodyData = request.httpBody, let httpBody = String(data: httpBodyData, encoding: .utf8) {
340             var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"")
341             escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"")
342
343             components.append("-d \"\(escapedBody)\"")
344         }
345
346         components.append("\"\(url.absoluteString)\"")
347
348         return components.joined(separator: " \\\n\t")
349     }
350 }
351
352 // MARK: -
353
354 /// Specific type of `Request` that manages an underlying `URLSessionDataTask`.
355 open class DataRequest: Request {
356
357     // MARK: Helper Types
358
359     struct Requestable: TaskConvertible {
360         let urlRequest: URLRequest
361
362         func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
363             do {
364                 let urlRequest = try self.urlRequest.adapt(using: adapter)
365                 return queue.sync { session.dataTask(with: urlRequest) }
366             } catch {
367                 throw AdaptError(error: error)
368             }
369         }
370     }
371
372     // MARK: Properties
373
374     /// The request sent or to be sent to the server.
375     open override var request: URLRequest? {
376         if let request = super.request { return request }
377         if let requestable = originalTask as? Requestable { return requestable.urlRequest }
378
379         return nil
380     }
381
382     /// The progress of fetching the response data from the server for the request.
383     open var progress: Progress { return dataDelegate.progress }
384
385     var dataDelegate: DataTaskDelegate { return delegate as! DataTaskDelegate }
386
387     // MARK: Stream
388
389     /// Sets a closure to be called periodically during the lifecycle of the request as data is read from the server.
390     ///
391     /// This closure returns the bytes most recently received from the server, not including data from previous calls.
392     /// If this closure is set, data will only be available within this closure, and will not be saved elsewhere. It is
393     /// also important to note that the server data in any `Response` object will be `nil`.
394     ///
395     /// - parameter closure: The code to be executed periodically during the lifecycle of the request.
396     ///
397     /// - returns: The request.
398     @discardableResult
399     open func stream(closure: ((Data) -> Void)? = nil) -> Self {
400         dataDelegate.dataStream = closure
401         return self
402     }
403
404     // MARK: Progress
405
406     /// Sets a closure to be called periodically during the lifecycle of the `Request` as data is read from the server.
407     ///
408     /// - parameter queue:   The dispatch queue to execute the closure on.
409     /// - parameter closure: The code to be executed periodically as data is read from the server.
410     ///
411     /// - returns: The request.
412     @discardableResult
413     open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
414         dataDelegate.progressHandler = (closure, queue)
415         return self
416     }
417 }
418
419 // MARK: -
420
421 /// Specific type of `Request` that manages an underlying `URLSessionDownloadTask`.
422 open class DownloadRequest: Request {
423
424     // MARK: Helper Types
425
426     /// A collection of options to be executed prior to moving a downloaded file from the temporary URL to the
427     /// destination URL.
428     public struct DownloadOptions: OptionSet {
429         /// Returns the raw bitmask value of the option and satisfies the `RawRepresentable` protocol.
430         public let rawValue: UInt
431
432         /// A `DownloadOptions` flag that creates intermediate directories for the destination URL if specified.
433         public static let createIntermediateDirectories = DownloadOptions(rawValue: 1 << 0)
434
435         /// A `DownloadOptions` flag that removes a previous file from the destination URL if specified.
436         public static let removePreviousFile = DownloadOptions(rawValue: 1 << 1)
437
438         /// Creates a `DownloadFileDestinationOptions` instance with the specified raw value.
439         ///
440         /// - parameter rawValue: The raw bitmask value for the option.
441         ///
442         /// - returns: A new log level instance.
443         public init(rawValue: UInt) {
444             self.rawValue = rawValue
445         }
446     }
447
448     /// A closure executed once a download request has successfully completed in order to determine where to move the
449     /// temporary file written to during the download process. The closure takes two arguments: the temporary file URL
450     /// and the URL response, and returns a two arguments: the file URL where the temporary file should be moved and
451     /// the options defining how the file should be moved.
452     public typealias DownloadFileDestination = (
453         _ temporaryURL: URL,
454         _ response: HTTPURLResponse)
455         -> (destinationURL: URL, options: DownloadOptions)
456
457     enum Downloadable: TaskConvertible {
458         case request(URLRequest)
459         case resumeData(Data)
460
461         func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
462             do {
463                 let task: URLSessionTask
464
465                 switch self {
466                 case let .request(urlRequest):
467                     let urlRequest = try urlRequest.adapt(using: adapter)
468                     task = queue.sync { session.downloadTask(with: urlRequest) }
469                 case let .resumeData(resumeData):
470                     task = queue.sync { session.downloadTask(withResumeData: resumeData) }
471                 }
472
473                 return task
474             } catch {
475                 throw AdaptError(error: error)
476             }
477         }
478     }
479
480     // MARK: Properties
481
482     /// The request sent or to be sent to the server.
483     open override var request: URLRequest? {
484         if let request = super.request { return request }
485
486         if let downloadable = originalTask as? Downloadable, case let .request(urlRequest) = downloadable {
487             return urlRequest
488         }
489
490         return nil
491     }
492
493     /// The resume data of the underlying download task if available after a failure.
494     open var resumeData: Data? { return downloadDelegate.resumeData }
495
496     /// The progress of downloading the response data from the server for the request.
497     open var progress: Progress { return downloadDelegate.progress }
498
499     var downloadDelegate: DownloadTaskDelegate { return delegate as! DownloadTaskDelegate }
500
501     // MARK: State
502
503     /// Cancels the request.
504     open override func cancel() {
505         downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 }
506
507         NotificationCenter.default.post(
508             name: Notification.Name.Task.DidCancel,
509             object: self,
510             userInfo: [Notification.Key.Task: task as Any]
511         )
512     }
513
514     // MARK: Progress
515
516     /// Sets a closure to be called periodically during the lifecycle of the `Request` as data is read from the server.
517     ///
518     /// - parameter queue:   The dispatch queue to execute the closure on.
519     /// - parameter closure: The code to be executed periodically as data is read from the server.
520     ///
521     /// - returns: The request.
522     @discardableResult
523     open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
524         downloadDelegate.progressHandler = (closure, queue)
525         return self
526     }
527
528     // MARK: Destination
529
530     /// Creates a download file destination closure which uses the default file manager to move the temporary file to a
531     /// file URL in the first available directory with the specified search path directory and search path domain mask.
532     ///
533     /// - parameter directory: The search path directory. `.DocumentDirectory` by default.
534     /// - parameter domain:    The search path domain mask. `.UserDomainMask` by default.
535     ///
536     /// - returns: A download file destination closure.
537     open class func suggestedDownloadDestination(
538         for directory: FileManager.SearchPathDirectory = .documentDirectory,
539         in domain: FileManager.SearchPathDomainMask = .userDomainMask)
540         -> DownloadFileDestination
541     {
542         return { temporaryURL, response in
543             let directoryURLs = FileManager.default.urls(for: directory, in: domain)
544
545             if !directoryURLs.isEmpty {
546                 return (directoryURLs[0].appendingPathComponent(response.suggestedFilename!), [])
547             }
548
549             return (temporaryURL, [])
550         }
551     }
552 }
553
554 // MARK: -
555
556 /// Specific type of `Request` that manages an underlying `URLSessionUploadTask`.
557 open class UploadRequest: DataRequest {
558
559     // MARK: Helper Types
560
561     enum Uploadable: TaskConvertible {
562         case data(Data, URLRequest)
563         case file(URL, URLRequest)
564         case stream(InputStream, URLRequest)
565
566         func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
567             do {
568                 let task: URLSessionTask
569
570                 switch self {
571                 case let .data(data, urlRequest):
572                     let urlRequest = try urlRequest.adapt(using: adapter)
573                     task = queue.sync { session.uploadTask(with: urlRequest, from: data) }
574                 case let .file(url, urlRequest):
575                     let urlRequest = try urlRequest.adapt(using: adapter)
576                     task = queue.sync { session.uploadTask(with: urlRequest, fromFile: url) }
577                 case let .stream(_, urlRequest):
578                     let urlRequest = try urlRequest.adapt(using: adapter)
579                     task = queue.sync { session.uploadTask(withStreamedRequest: urlRequest) }
580                 }
581
582                 return task
583             } catch {
584                 throw AdaptError(error: error)
585             }
586         }
587     }
588
589     // MARK: Properties
590
591     /// The request sent or to be sent to the server.
592     open override var request: URLRequest? {
593         if let request = super.request { return request }
594
595         guard let uploadable = originalTask as? Uploadable else { return nil }
596
597         switch uploadable {
598         case .data(_, let urlRequest), .file(_, let urlRequest), .stream(_, let urlRequest):
599             return urlRequest
600         }
601     }
602
603     /// The progress of uploading the payload to the server for the upload request.
604     open var uploadProgress: Progress { return uploadDelegate.uploadProgress }
605
606     var uploadDelegate: UploadTaskDelegate { return delegate as! UploadTaskDelegate }
607
608     // MARK: Upload Progress
609
610     /// Sets a closure to be called periodically during the lifecycle of the `UploadRequest` as data is sent to
611     /// the server.
612     ///
613     /// After the data is sent to the server, the `progress(queue:closure:)` APIs can be used to monitor the progress
614     /// of data being read from the server.
615     ///
616     /// - parameter queue:   The dispatch queue to execute the closure on.
617     /// - parameter closure: The code to be executed periodically as data is sent to the server.
618     ///
619     /// - returns: The request.
620     @discardableResult
621     open func uploadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
622         uploadDelegate.uploadProgressHandler = (closure, queue)
623         return self
624     }
625 }
626
627 // MARK: -
628
629 #if !os(watchOS)
630
631 /// Specific type of `Request` that manages an underlying `URLSessionStreamTask`.
632 @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
633 open class StreamRequest: Request {
634     enum Streamable: TaskConvertible {
635         case stream(hostName: String, port: Int)
636         case netService(NetService)
637
638         func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
639             let task: URLSessionTask
640
641             switch self {
642             case let .stream(hostName, port):
643                 task = queue.sync { session.streamTask(withHostName: hostName, port: port) }
644             case let .netService(netService):
645                 task = queue.sync { session.streamTask(with: netService) }
646             }
647
648             return task
649         }
650     }
651 }
652
653 #endif