123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- //
- // Validation.swift
- //
- // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- import Foundation
- extension Request {
- // MARK: Helper Types
- fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason
- /// Used to represent whether a validation succeeded or failed.
- public typealias ValidationResult = Result<Void, Error>
- fileprivate struct MIMEType {
- let type: String
- let subtype: String
- var isWildcard: Bool { type == "*" && subtype == "*" }
- init?(_ string: String) {
- let components: [String] = {
- let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines)
- let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)]
- return split.components(separatedBy: "/")
- }()
- if let type = components.first, let subtype = components.last {
- self.type = type
- self.subtype = subtype
- } else {
- return nil
- }
- }
- func matches(_ mime: MIMEType) -> Bool {
- switch (type, subtype) {
- case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"):
- return true
- default:
- return false
- }
- }
- }
- // MARK: Properties
- fileprivate var acceptableStatusCodes: Range<Int> { 200..<300 }
- fileprivate var acceptableContentTypes: [String] {
- if let accept = request?.value(forHTTPHeaderField: "Accept") {
- return accept.components(separatedBy: ",")
- }
- return ["*/*"]
- }
- // MARK: Status Code
- fileprivate func validate<S: Sequence>(statusCode acceptableStatusCodes: S,
- response: HTTPURLResponse)
- -> ValidationResult
- where S.Iterator.Element == Int {
- if acceptableStatusCodes.contains(response.statusCode) {
- return .success(())
- } else {
- let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
- return .failure(AFError.responseValidationFailed(reason: reason))
- }
- }
- // MARK: Content Type
- fileprivate func validate<S: Sequence>(contentType acceptableContentTypes: S,
- response: HTTPURLResponse,
- data: Data?)
- -> ValidationResult
- where S.Iterator.Element == String {
- guard let data = data, !data.isEmpty else { return .success(()) }
- return validate(contentType: acceptableContentTypes, response: response)
- }
- fileprivate func validate<S: Sequence>(contentType acceptableContentTypes: S,
- response: HTTPURLResponse)
- -> ValidationResult
- where S.Iterator.Element == String {
- guard
- let responseContentType = response.mimeType,
- let responseMIMEType = MIMEType(responseContentType)
- else {
- for contentType in acceptableContentTypes {
- if let mimeType = MIMEType(contentType), mimeType.isWildcard {
- return .success(())
- }
- }
- let error: AFError = {
- let reason: ErrorReason = .missingContentType(acceptableContentTypes: acceptableContentTypes.sorted())
- return AFError.responseValidationFailed(reason: reason)
- }()
- return .failure(error)
- }
- for contentType in acceptableContentTypes {
- if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) {
- return .success(())
- }
- }
- let error: AFError = {
- let reason: ErrorReason = .unacceptableContentType(acceptableContentTypes: acceptableContentTypes.sorted(),
- responseContentType: responseContentType)
- return AFError.responseValidationFailed(reason: reason)
- }()
- return .failure(error)
- }
- }
- // MARK: -
- extension DataRequest {
- /// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the
- /// request was valid.
- public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
- /// Validates that the response has a status code in the specified sequence.
- ///
- /// If validation fails, subsequent calls to response handlers will have an associated error.
- ///
- /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes.
- ///
- /// - Returns: The instance.
- @discardableResult
- public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
- validate { [unowned self] _, response, _ in
- self.validate(statusCode: acceptableStatusCodes, response: response)
- }
- }
- /// Validates that the response has a content type in the specified sequence.
- ///
- /// If validation fails, subsequent calls to response handlers will have an associated error.
- ///
- /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
- ///
- /// - returns: The request.
- @discardableResult
- public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String {
- validate { [unowned self] _, response, data in
- self.validate(contentType: acceptableContentTypes(), response: response, data: data)
- }
- }
- /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
- /// type matches any specified in the Accept HTTP header field.
- ///
- /// If validation fails, subsequent calls to response handlers will have an associated error.
- ///
- /// - returns: The request.
- @discardableResult
- public func validate() -> Self {
- let contentTypes: () -> [String] = { [unowned self] in
- acceptableContentTypes
- }
- return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
- }
- }
- extension DataStreamRequest {
- /// A closure used to validate a request that takes a `URLRequest` and `HTTPURLResponse` and returns whether the
- /// request was valid.
- public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse) -> ValidationResult
- /// Validates that the response has a status code in the specified sequence.
- ///
- /// If validation fails, subsequent calls to response handlers will have an associated error.
- ///
- /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes.
- ///
- /// - Returns: The instance.
- @discardableResult
- public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
- validate { [unowned self] _, response in
- self.validate(statusCode: acceptableStatusCodes, response: response)
- }
- }
- /// Validates that the response has a content type in the specified sequence.
- ///
- /// If validation fails, subsequent calls to response handlers will have an associated error.
- ///
- /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
- ///
- /// - returns: The request.
- @discardableResult
- public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String {
- validate { [unowned self] _, response in
- self.validate(contentType: acceptableContentTypes(), response: response)
- }
- }
- /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
- /// type matches any specified in the Accept HTTP header field.
- ///
- /// If validation fails, subsequent calls to response handlers will have an associated error.
- ///
- /// - Returns: The instance.
- @discardableResult
- public func validate() -> Self {
- let contentTypes: () -> [String] = { [unowned self] in
- acceptableContentTypes
- }
- return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
- }
- }
- // MARK: -
- extension DownloadRequest {
- /// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a
- /// destination URL, and returns whether the request was valid.
- public typealias Validation = (_ request: URLRequest?,
- _ response: HTTPURLResponse,
- _ fileURL: URL?)
- -> ValidationResult
- /// Validates that the response has a status code in the specified sequence.
- ///
- /// If validation fails, subsequent calls to response handlers will have an associated error.
- ///
- /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes.
- ///
- /// - Returns: The instance.
- @discardableResult
- public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
- validate { [unowned self] _, response, _ in
- self.validate(statusCode: acceptableStatusCodes, response: response)
- }
- }
- /// Validates that the response has a content type in the specified sequence.
- ///
- /// If validation fails, subsequent calls to response handlers will have an associated error.
- ///
- /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
- ///
- /// - returns: The request.
- @discardableResult
- public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String {
- validate { [unowned self] _, response, fileURL in
- guard let validFileURL = fileURL else {
- return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
- }
- do {
- let data = try Data(contentsOf: validFileURL)
- return self.validate(contentType: acceptableContentTypes(), response: response, data: data)
- } catch {
- return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL)))
- }
- }
- }
- /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
- /// type matches any specified in the Accept HTTP header field.
- ///
- /// If validation fails, subsequent calls to response handlers will have an associated error.
- ///
- /// - returns: The request.
- @discardableResult
- public func validate() -> Self {
- let contentTypes = { [unowned self] in
- acceptableContentTypes
- }
- return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
- }
- }
|