You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

315 lines
12 KiB

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