added iOS source code
[wl-app.git] / iOS / Pods / Alamofire / Source / Validation.swift
1 //
2 //  Validation.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 extension Request {
28
29     // MARK: Helper Types
30
31     fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason
32
33     /// Used to represent whether validation was successful or encountered an error resulting in a failure.
34     ///
35     /// - success: The validation was successful.
36     /// - failure: The validation failed encountering the provided error.
37     public enum ValidationResult {
38         case success
39         case failure(Error)
40     }
41
42     fileprivate struct MIMEType {
43         let type: String
44         let subtype: String
45
46         var isWildcard: Bool { return type == "*" && subtype == "*" }
47
48         init?(_ string: String) {
49             let components: [String] = {
50                 let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines)
51
52             #if swift(>=3.2)
53                 let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)]
54             #else
55                 let split = stripped.substring(to: stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)
56             #endif
57
58                 return split.components(separatedBy: "/")
59             }()
60
61             if let type = components.first, let subtype = components.last {
62                 self.type = type
63                 self.subtype = subtype
64             } else {
65                 return nil
66             }
67         }
68
69         func matches(_ mime: MIMEType) -> Bool {
70             switch (type, subtype) {
71             case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"):
72                 return true
73             default:
74                 return false
75             }
76         }
77     }
78
79     // MARK: Properties
80
81     fileprivate var acceptableStatusCodes: [Int] { return Array(200..<300) }
82
83     fileprivate var acceptableContentTypes: [String] {
84         if let accept = request?.value(forHTTPHeaderField: "Accept") {
85             return accept.components(separatedBy: ",")
86         }
87
88         return ["*/*"]
89     }
90
91     // MARK: Status Code
92
93     fileprivate func validate<S: Sequence>(
94         statusCode acceptableStatusCodes: S,
95         response: HTTPURLResponse)
96         -> ValidationResult
97         where S.Iterator.Element == Int
98     {
99         if acceptableStatusCodes.contains(response.statusCode) {
100             return .success
101         } else {
102             let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
103             return .failure(AFError.responseValidationFailed(reason: reason))
104         }
105     }
106
107     // MARK: Content Type
108
109     fileprivate func validate<S: Sequence>(
110         contentType acceptableContentTypes: S,
111         response: HTTPURLResponse,
112         data: Data?)
113         -> ValidationResult
114         where S.Iterator.Element == String
115     {
116         guard let data = data, data.count > 0 else { return .success }
117
118         guard
119             let responseContentType = response.mimeType,
120             let responseMIMEType = MIMEType(responseContentType)
121         else {
122             for contentType in acceptableContentTypes {
123                 if let mimeType = MIMEType(contentType), mimeType.isWildcard {
124                     return .success
125                 }
126             }
127
128             let error: AFError = {
129                 let reason: ErrorReason = .missingContentType(acceptableContentTypes: Array(acceptableContentTypes))
130                 return AFError.responseValidationFailed(reason: reason)
131             }()
132
133             return .failure(error)
134         }
135
136         for contentType in acceptableContentTypes {
137             if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) {
138                 return .success
139             }
140         }
141
142         let error: AFError = {
143             let reason: ErrorReason = .unacceptableContentType(
144                 acceptableContentTypes: Array(acceptableContentTypes),
145                 responseContentType: responseContentType
146             )
147
148             return AFError.responseValidationFailed(reason: reason)
149         }()
150
151         return .failure(error)
152     }
153 }
154
155 // MARK: -
156
157 extension DataRequest {
158     /// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the
159     /// request was valid.
160     public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
161
162     /// Validates the request, using the specified closure.
163     ///
164     /// If validation fails, subsequent calls to response handlers will have an associated error.
165     ///
166     /// - parameter validation: A closure to validate the request.
167     ///
168     /// - returns: The request.
169     @discardableResult
170     public func validate(_ validation: @escaping Validation) -> Self {
171         let validationExecution: () -> Void = { [unowned self] in
172             if
173                 let response = self.response,
174                 self.delegate.error == nil,
175                 case let .failure(error) = validation(self.request, response, self.delegate.data)
176             {
177                 self.delegate.error = error
178             }
179         }
180
181         validations.append(validationExecution)
182
183         return self
184     }
185
186     /// Validates that the response has a status code in the specified sequence.
187     ///
188     /// If validation fails, subsequent calls to response handlers will have an associated error.
189     ///
190     /// - parameter range: The range of acceptable status codes.
191     ///
192     /// - returns: The request.
193     @discardableResult
194     public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
195         return validate { [unowned self] _, response, _ in
196             return self.validate(statusCode: acceptableStatusCodes, response: response)
197         }
198     }
199
200     /// Validates that the response has a content type in the specified sequence.
201     ///
202     /// If validation fails, subsequent calls to response handlers will have an associated error.
203     ///
204     /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
205     ///
206     /// - returns: The request.
207     @discardableResult
208     public func validate<S: Sequence>(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String {
209         return validate { [unowned self] _, response, data in
210             return self.validate(contentType: acceptableContentTypes, response: response, data: data)
211         }
212     }
213
214     /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
215     /// type matches any specified in the Accept HTTP header field.
216     ///
217     /// If validation fails, subsequent calls to response handlers will have an associated error.
218     ///
219     /// - returns: The request.
220     @discardableResult
221     public func validate() -> Self {
222         return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes)
223     }
224 }
225
226 // MARK: -
227
228 extension DownloadRequest {
229     /// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a
230     /// destination URL, and returns whether the request was valid.
231     public typealias Validation = (
232         _ request: URLRequest?,
233         _ response: HTTPURLResponse,
234         _ temporaryURL: URL?,
235         _ destinationURL: URL?)
236         -> ValidationResult
237
238     /// Validates the request, using the specified closure.
239     ///
240     /// If validation fails, subsequent calls to response handlers will have an associated error.
241     ///
242     /// - parameter validation: A closure to validate the request.
243     ///
244     /// - returns: The request.
245     @discardableResult
246     public func validate(_ validation: @escaping Validation) -> Self {
247         let validationExecution: () -> Void = { [unowned self] in
248             let request = self.request
249             let temporaryURL = self.downloadDelegate.temporaryURL
250             let destinationURL = self.downloadDelegate.destinationURL
251
252             if
253                 let response = self.response,
254                 self.delegate.error == nil,
255                 case let .failure(error) = validation(request, response, temporaryURL, destinationURL)
256             {
257                 self.delegate.error = error
258             }
259         }
260
261         validations.append(validationExecution)
262
263         return self
264     }
265
266     /// Validates that the response has a status code in the specified sequence.
267     ///
268     /// If validation fails, subsequent calls to response handlers will have an associated error.
269     ///
270     /// - parameter range: The range of acceptable status codes.
271     ///
272     /// - returns: The request.
273     @discardableResult
274     public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
275         return validate { [unowned self] _, response, _, _ in
276             return self.validate(statusCode: acceptableStatusCodes, response: response)
277         }
278     }
279
280     /// Validates that the response has a content type in the specified sequence.
281     ///
282     /// If validation fails, subsequent calls to response handlers will have an associated error.
283     ///
284     /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
285     ///
286     /// - returns: The request.
287     @discardableResult
288     public func validate<S: Sequence>(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String {
289         return validate { [unowned self] _, response, _, _ in
290             let fileURL = self.downloadDelegate.fileURL
291
292             guard let validFileURL = fileURL else {
293                 return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
294             }
295
296             do {
297                 let data = try Data(contentsOf: validFileURL)
298                 return self.validate(contentType: acceptableContentTypes, response: response, data: data)
299             } catch {
300                 return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL)))
301             }
302         }
303     }
304
305     /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
306     /// type matches any specified in the Accept HTTP header field.
307     ///
308     /// If validation fails, subsequent calls to response handlers will have an associated error.
309     ///
310     /// - returns: The request.
311     @discardableResult
312     public func validate() -> Self {
313         return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes)
314     }
315 }