4 // Copyright (c) 2014-2017 Alamofire Software Foundation (http://alamofire.org/)
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:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
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
31 fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason
33 /// Used to represent whether validation was successful or encountered an error resulting in a failure.
35 /// - success: The validation was successful.
36 /// - failure: The validation failed encountering the provided error.
37 public enum ValidationResult {
42 fileprivate struct MIMEType {
46 var isWildcard: Bool { return type == "*" && subtype == "*" }
48 init?(_ string: String) {
49 let components: [String] = {
50 let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines)
53 let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)]
55 let split = stripped.substring(to: stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)
58 return split.components(separatedBy: "/")
61 if let type = components.first, let subtype = components.last {
63 self.subtype = subtype
69 func matches(_ mime: MIMEType) -> Bool {
70 switch (type, subtype) {
71 case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"):
81 fileprivate var acceptableStatusCodes: [Int] { return Array(200..<300) }
83 fileprivate var acceptableContentTypes: [String] {
84 if let accept = request?.value(forHTTPHeaderField: "Accept") {
85 return accept.components(separatedBy: ",")
93 fileprivate func validate<S: Sequence>(
94 statusCode acceptableStatusCodes: S,
95 response: HTTPURLResponse)
97 where S.Iterator.Element == Int
99 if acceptableStatusCodes.contains(response.statusCode) {
102 let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
103 return .failure(AFError.responseValidationFailed(reason: reason))
107 // MARK: Content Type
109 fileprivate func validate<S: Sequence>(
110 contentType acceptableContentTypes: S,
111 response: HTTPURLResponse,
114 where S.Iterator.Element == String
116 guard let data = data, data.count > 0 else { return .success }
119 let responseContentType = response.mimeType,
120 let responseMIMEType = MIMEType(responseContentType)
122 for contentType in acceptableContentTypes {
123 if let mimeType = MIMEType(contentType), mimeType.isWildcard {
128 let error: AFError = {
129 let reason: ErrorReason = .missingContentType(acceptableContentTypes: Array(acceptableContentTypes))
130 return AFError.responseValidationFailed(reason: reason)
133 return .failure(error)
136 for contentType in acceptableContentTypes {
137 if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) {
142 let error: AFError = {
143 let reason: ErrorReason = .unacceptableContentType(
144 acceptableContentTypes: Array(acceptableContentTypes),
145 responseContentType: responseContentType
148 return AFError.responseValidationFailed(reason: reason)
151 return .failure(error)
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
162 /// Validates the request, using the specified closure.
164 /// If validation fails, subsequent calls to response handlers will have an associated error.
166 /// - parameter validation: A closure to validate the request.
168 /// - returns: The request.
170 public func validate(_ validation: @escaping Validation) -> Self {
171 let validationExecution: () -> Void = { [unowned self] in
173 let response = self.response,
174 self.delegate.error == nil,
175 case let .failure(error) = validation(self.request, response, self.delegate.data)
177 self.delegate.error = error
181 validations.append(validationExecution)
186 /// Validates that the response has a status code in the specified sequence.
188 /// If validation fails, subsequent calls to response handlers will have an associated error.
190 /// - parameter range: The range of acceptable status codes.
192 /// - returns: The request.
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)
200 /// Validates that the response has a content type in the specified sequence.
202 /// If validation fails, subsequent calls to response handlers will have an associated error.
204 /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
206 /// - returns: The request.
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)
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.
217 /// If validation fails, subsequent calls to response handlers will have an associated error.
219 /// - returns: The request.
221 public func validate() -> Self {
222 return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes)
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?)
238 /// Validates the request, using the specified closure.
240 /// If validation fails, subsequent calls to response handlers will have an associated error.
242 /// - parameter validation: A closure to validate the request.
244 /// - returns: The request.
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
253 let response = self.response,
254 self.delegate.error == nil,
255 case let .failure(error) = validation(request, response, temporaryURL, destinationURL)
257 self.delegate.error = error
261 validations.append(validationExecution)
266 /// Validates that the response has a status code in the specified sequence.
268 /// If validation fails, subsequent calls to response handlers will have an associated error.
270 /// - parameter range: The range of acceptable status codes.
272 /// - returns: The request.
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)
280 /// Validates that the response has a content type in the specified sequence.
282 /// If validation fails, subsequent calls to response handlers will have an associated error.
284 /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
286 /// - returns: The request.
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
292 guard let validFileURL = fileURL else {
293 return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
297 let data = try Data(contentsOf: validFileURL)
298 return self.validate(contentType: acceptableContentTypes, response: response, data: data)
300 return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL)))
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.
308 /// If validation fails, subsequent calls to response handlers will have an associated error.
310 /// - returns: The request.
312 public func validate() -> Self {
313 return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes)