Validation.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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. let contentTypes = { [unowned self] in
  188. self.acceptableContentTypes
  189. }
  190. return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
  191. }
  192. }
  193. // MARK: -
  194. extension DownloadRequest {
  195. /// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a
  196. /// destination URL, and returns whether the request was valid.
  197. public typealias Validation = (
  198. _ request: URLRequest?,
  199. _ response: HTTPURLResponse,
  200. _ temporaryURL: URL?,
  201. _ destinationURL: URL?)
  202. -> ValidationResult
  203. /// Validates the request, using the specified closure.
  204. ///
  205. /// If validation fails, subsequent calls to response handlers will have an associated error.
  206. ///
  207. /// - parameter validation: A closure to validate the request.
  208. ///
  209. /// - returns: The request.
  210. @discardableResult
  211. public func validate(_ validation: @escaping Validation) -> Self {
  212. let validationExecution: () -> Void = { [unowned self] in
  213. let request = self.request
  214. let temporaryURL = self.downloadDelegate.temporaryURL
  215. let destinationURL = self.downloadDelegate.destinationURL
  216. if
  217. let response = self.response,
  218. self.delegate.error == nil,
  219. case let .failure(error) = validation(request, response, temporaryURL, destinationURL)
  220. {
  221. self.delegate.error = error
  222. }
  223. }
  224. validations.append(validationExecution)
  225. return self
  226. }
  227. /// Validates that the response has a status code in the specified sequence.
  228. ///
  229. /// If validation fails, subsequent calls to response handlers will have an associated error.
  230. ///
  231. /// - parameter range: The range of acceptable status codes.
  232. ///
  233. /// - returns: The request.
  234. @discardableResult
  235. public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
  236. return validate { [unowned self] _, response, _, _ in
  237. return self.validate(statusCode: acceptableStatusCodes, response: response)
  238. }
  239. }
  240. /// Validates that the response has a content type in the specified sequence.
  241. ///
  242. /// If validation fails, subsequent calls to response handlers will have an associated error.
  243. ///
  244. /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
  245. ///
  246. /// - returns: The request.
  247. @discardableResult
  248. public func validate<S: Sequence>(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String {
  249. return validate { [unowned self] _, response, _, _ in
  250. let fileURL = self.downloadDelegate.fileURL
  251. guard let validFileURL = fileURL else {
  252. return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
  253. }
  254. do {
  255. let data = try Data(contentsOf: validFileURL)
  256. return self.validate(contentType: acceptableContentTypes, response: response, data: data)
  257. } catch {
  258. return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL)))
  259. }
  260. }
  261. }
  262. /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
  263. /// type matches any specified in the Accept HTTP header field.
  264. ///
  265. /// If validation fails, subsequent calls to response handlers will have an associated error.
  266. ///
  267. /// - returns: The request.
  268. @discardableResult
  269. public func validate() -> Self {
  270. let contentTypes = { [unowned self] in
  271. self.acceptableContentTypes
  272. }
  273. return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
  274. }
  275. }