Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/httpheaders' into featur…
Browse files Browse the repository at this point in the history
…e/encodable-requests

# Conflicts:
#	Source/AFError.swift
  • Loading branch information
jshier committed Nov 24, 2018
2 parents 34f3668 + ea48749 commit ae8fde4
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 99 deletions.
16 changes: 8 additions & 8 deletions Alamofire.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
3107EA3F20A1267C00445260 /* SessionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9DCE771CB1BCE2003E6463 /* SessionDelegateTests.swift */; };
3107EA4020A1267C00445260 /* SessionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9DCE771CB1BCE2003E6463 /* SessionDelegateTests.swift */; };
3107EA4120A1267D00445260 /* SessionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9DCE771CB1BCE2003E6463 /* SessionDelegateTests.swift */; };
3111CE8420A7636E008315E2 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D1C6F419D52968002E74FE /* SessionManagerTests.swift */; };
3111CE8520A7636F008315E2 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D1C6F419D52968002E74FE /* SessionManagerTests.swift */; };
3111CE8620A76370008315E2 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D1C6F419D52968002E74FE /* SessionManagerTests.swift */; };
3111CE8420A7636E008315E2 /* SessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D1C6F419D52968002E74FE /* SessionTests.swift */; };
3111CE8520A7636F008315E2 /* SessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D1C6F419D52968002E74FE /* SessionTests.swift */; };
3111CE8620A76370008315E2 /* SessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D1C6F419D52968002E74FE /* SessionTests.swift */; };
3111CE8820A77843008315E2 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111CE8720A77843008315E2 /* EventMonitor.swift */; };
3111CE8920A77944008315E2 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111CE8720A77843008315E2 /* EventMonitor.swift */; };
3111CE8A20A77945008315E2 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111CE8720A77843008315E2 /* EventMonitor.swift */; };
Expand Down Expand Up @@ -445,7 +445,7 @@
F86AEFE51AE6A282007D9C76 /* TLSEvaluationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLSEvaluationTests.swift; sourceTree = "<group>"; };
F897FF4019AA800700AB5182 /* Alamofire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Alamofire.swift; sourceTree = "<group>"; };
F8AE910119D28DCC0078C7B2 /* ValidationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationTests.swift; sourceTree = "<group>"; };
F8D1C6F419D52968002E74FE /* SessionManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionManagerTests.swift; sourceTree = "<group>"; };
F8D1C6F419D52968002E74FE /* SessionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionTests.swift; sourceTree = "<group>"; };
F8E6024419CB46A800A3E7F1 /* AuthenticationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -516,7 +516,7 @@
F8111E5E19A9674D0040E7D1 /* ResponseTests.swift */,
4CA028C41B7466C500C84163 /* ResultTests.swift */,
4C9DCE771CB1BCE2003E6463 /* SessionDelegateTests.swift */,
F8D1C6F419D52968002E74FE /* SessionManagerTests.swift */,
F8D1C6F419D52968002E74FE /* SessionTests.swift */,
F8111E5F19A9674D0040E7D1 /* UploadTests.swift */,
);
name = Core;
Expand Down Expand Up @@ -1312,7 +1312,7 @@
4CF627141BA7CC240011A099 /* BaseTestCase.swift in Sources */,
31727424218BB9A50039FFCC /* HTTPBin.swift in Sources */,
31EBD9C320D1D89D00D1FF34 /* ValidationTests.swift in Sources */,
3111CE8620A76370008315E2 /* SessionManagerTests.swift in Sources */,
3111CE8620A76370008315E2 /* SessionTests.swift in Sources */,
31C2B0F220B271380089BA7C /* TLSEvaluationTests.swift in Sources */,
3111CE9D20A7EC58008315E2 /* URLProtocolTests.swift in Sources */,
317A6A7820B2208000A9FEC5 /* DownloadTests.swift in Sources */,
Expand Down Expand Up @@ -1445,7 +1445,7 @@
4C256A531B096C770065714F /* BaseTestCase.swift in Sources */,
31727422218BB9A50039FFCC /* HTTPBin.swift in Sources */,
31EBD9C120D1D89C00D1FF34 /* ValidationTests.swift in Sources */,
3111CE8420A7636E008315E2 /* SessionManagerTests.swift in Sources */,
3111CE8420A7636E008315E2 /* SessionTests.swift in Sources */,
31C2B0F020B271370089BA7C /* TLSEvaluationTests.swift in Sources */,
3111CE9B20A7EC57008315E2 /* URLProtocolTests.swift in Sources */,
317A6A7620B2207F00A9FEC5 /* DownloadTests.swift in Sources */,
Expand Down Expand Up @@ -1476,7 +1476,7 @@
F829C6BF1A7A950600A2CD59 /* RequestTests.swift in Sources */,
31727423218BB9A50039FFCC /* HTTPBin.swift in Sources */,
31EBD9C220D1D89C00D1FF34 /* ValidationTests.swift in Sources */,
3111CE8520A7636F008315E2 /* SessionManagerTests.swift in Sources */,
3111CE8520A7636F008315E2 /* SessionTests.swift in Sources */,
31C2B0F120B271370089BA7C /* TLSEvaluationTests.swift in Sources */,
3111CE9C20A7EC58008315E2 /* URLProtocolTests.swift in Sources */,
317A6A7720B2208000A9FEC5 /* DownloadTests.swift in Sources */,
Expand Down
7 changes: 1 addition & 6 deletions Source/AFError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,6 @@ public enum AFError: Error {
self.result = result
}
}
/// Trust evaluation was failed for an unknown reason. Usually this means the evaluator failed but didn't throw
/// an error.
case unknown(host: String)
/// `allHostsMustBeEvaluated` was `true` but no evaluator was found for the associated host.
case noRequiredEvaluator(host: String)
/// No certificates were found with which to perform the trust evaluation.
case noCertificatesFound
Expand Down Expand Up @@ -198,6 +194,7 @@ public enum AFError: Error {
}

extension Error {
/// Returns the instance cast as an `AFError`.
public var asAFError: AFError? {
return self as? AFError
}
Expand Down Expand Up @@ -581,8 +578,6 @@ extension AFError.ResponseValidationFailureReason {
extension AFError.ServerTrustFailureReason {
var localizedDescription: String {
switch self {
case .unknown:
return "Server trust evaluation failed but no specific error was thrown."
case let .noRequiredEvaluator(host):
return "A ServerTrustEvaluating value is required for host \(host) but none was found."
case .noCertificatesFound:
Expand Down
16 changes: 16 additions & 0 deletions Source/HTTPHeaders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ public struct HTTPHeaders {
dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) }
}

/// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`.
///
/// - Parameters:
/// - name: The `HTTPHeader` name.
/// - value: The `HTTPHeader value.
public mutating func add(name: String, value: String) {
update(HTTPHeader(name: name, value: value))
}

/// Case-insensitively updates or appends the provided `HTTPHeader` into the instance.
///
/// - Parameter header: The `HTTPHeader` to update or append.
public mutating func add(_ header: HTTPHeader) {
update(header)
}

/// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`.
///
/// - Parameters:
Expand Down
2 changes: 1 addition & 1 deletion Source/MultipartFormData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ open class MultipartFormData {
if let fileName = fileName { disposition += "; filename=\"\(fileName)\"" }

var headers: HTTPHeaders = [.contentDisposition(disposition)]
if let mimeType = mimeType { headers.update(.contentType(mimeType)) }
if let mimeType = mimeType { headers.add(.contentType(mimeType)) }

return headers
}
Expand Down
115 changes: 45 additions & 70 deletions Source/ServerTrustEvaluation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ public protocol ServerTrustEvaluating {
/// - trust: The `SecTrust` value to evaluate.
/// - host: The host for which to evaluate the `SecTrust` value.
/// - Returns: A `Bool` indicating whether the evaluator considers the `SecTrust` value valid for `host`.
/// - Throws: An `AFError.serverTrustEvaluationFailed` with an associated `ServerTrustFailureReason`.
func evaluate(_ trust: SecTrust, forHost host: String) throws -> Bool
func evaluate(_ trust: SecTrust, forHost host: String) throws
#endif
}

Expand All @@ -96,12 +95,10 @@ extension Array where Element == ServerTrustEvaluating {
/// - trust: The `SecTrust` value to evaluate.
/// - host: The host for which to evaluate the `SecTrust` value.
/// - Returns: Whether or not the evaluator considers the `SecTrust` value valid for `host`.
func evaluate(_ trust: SecTrust, forHost host: String) throws -> Bool {
func evaluate(_ trust: SecTrust, forHost host: String) throws {
for evaluator in self {
guard try evaluator.evaluate(trust, forHost: host) else { return false }
try evaluator.evaluate(trust, forHost: host)
}

return true
}
#endif
}
Expand All @@ -121,18 +118,12 @@ public final class DefaultTrustEvaluator: ServerTrustEvaluating {
self.validateHost = validateHost
}

public func evaluate(_ trust: SecTrust, forHost host: String) throws -> Bool {
try trust.validate(policy: .default) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, trust, status, result)))
}

public func evaluate(_ trust: SecTrust, forHost host: String) throws {
if validateHost {
try trust.validate(policy: .hostname(host)) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, trust, status, result)))
}
try trust.validateHost(host)
}

return true
try trust.performDefaultEvaluation(forHost: host)
}
}

Expand Down Expand Up @@ -193,24 +184,18 @@ public final class RevocationTrustEvaluator: ServerTrustEvaluating {
self.options = options
}

public func evaluate(_ trust: SecTrust, forHost host: String) throws -> Bool {
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
if performDefaultValidation {
try trust.validate(policy: .default) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, trust, status, result)))
}
try trust.performDefaultEvaluation(forHost: host)
}

if validateHost {
try trust.validate(policy: .hostname(host)) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, trust, status, result)))
}
try trust.validateHost(host)
}

try trust.validate(policy: .revocation(options: options)) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options))
}

return true
}
}

Expand Down Expand Up @@ -248,7 +233,7 @@ public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating {
self.validateHost = validateHost
}

public func evaluate(_ trust: SecTrust, forHost host: String) throws -> Bool {
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
guard !certificates.isEmpty else {
throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound)
}
Expand All @@ -258,28 +243,22 @@ public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating {
}

if performDefaultValidation {
try trust.validate(policy: .default) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, trust, status, result)))
}
try trust.performDefaultEvaluation(forHost: host)
}

if validateHost {
try trust.validate(policy: .hostname(host)) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, trust, status, result)))
}
try trust.validateHost(host)
}

let serverCertificatesData = Set(trust.certificateData)
let pinnedCertificatesData = Set(certificates.data)
let certificatesPinned = !serverCertificatesData.isDisjoint(with: pinnedCertificatesData)
if !certificatesPinned {
let pinnedCertificatesInServerData = !serverCertificatesData.isDisjoint(with: pinnedCertificatesData)
if !pinnedCertificatesInServerData {
throw AFError.serverTrustEvaluationFailed(reason: .certificatePinningFailed(host: host,
trust: trust,
pinnedCertificates: certificates,
serverCertificates: trust.certificates))
}

return certificatesPinned
}
}

Expand Down Expand Up @@ -314,24 +293,20 @@ public final class PublicKeysTrustEvaluator: ServerTrustEvaluating {
self.validateHost = validateHost
}

public func evaluate(_ trust: SecTrust, forHost host: String) throws -> Bool {
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
guard !keys.isEmpty else {
throw AFError.serverTrustEvaluationFailed(reason: .noPublicKeysFound)
}

if performDefaultValidation {
try trust.validate(policy: .default) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, trust, status, result)))
}
try trust.performDefaultEvaluation(forHost: host)
}

if validateHost {
try trust.validate(policy: .hostname(host)) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, trust, status, result)))
}
try trust.validateHost(host)
}

let keysPinned: Bool = {
let pinnedKeysInServerKeys: Bool = {
for serverPublicKey in trust.publicKeys as [AnyHashable] {
for pinnedPublicKey in keys as [AnyHashable] {
if serverPublicKey == pinnedPublicKey {
Expand All @@ -342,14 +317,12 @@ public final class PublicKeysTrustEvaluator: ServerTrustEvaluating {
return false
}()

if !keysPinned {
if !pinnedKeysInServerKeys {
throw AFError.serverTrustEvaluationFailed(reason: .publicKeyPinningFailed(host: host,
trust: trust,
pinnedKeys: keys,
serverKeys: trust.publicKeys))
}

return keysPinned
}
}

Expand All @@ -365,8 +338,8 @@ public final class CompositeTrustEvaluator: ServerTrustEvaluating {
self.evaluators = evaluators
}

public func evaluate(_ trust: SecTrust, forHost host: String) throws -> Bool {
return try evaluators.evaluate(trust, forHost: host)
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
try evaluators.evaluate(trust, forHost: host)
}
}

Expand All @@ -376,9 +349,7 @@ public final class CompositeTrustEvaluator: ServerTrustEvaluating {
public final class DisabledEvaluator: ServerTrustEvaluating {
public init() { }

public func evaluate(_ trust: SecTrust, forHost host: String) throws -> Bool {
return true
}
public func evaluate(_ trust: SecTrust, forHost host: String) throws { }
}

extension Bundle {
Expand Down Expand Up @@ -408,29 +379,17 @@ extension Bundle {
}

public extension SecTrust {
/// Evaluates `self` and returns `true` if the evaluation succeeds with a value of `.unspecified` or `.proceed`.
var isValid: Bool {
var result = SecTrustResultType.invalid
let status = SecTrustEvaluate(self, &result)

return (status.isSuccess) ? (result == .unspecified || result == .proceed) : false
}

@discardableResult
func validate(policy: SecPolicy, errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws -> Bool {
return try apply(policy: policy).isValid(errorProducer: errorProducer)
func validate(policy: SecPolicy, errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
try apply(policy: policy).validate(errorProducer: errorProducer)
}

@discardableResult
func isValid(errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws -> Bool {
func validate(errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
var result = SecTrustResultType.invalid
let status = SecTrustEvaluate(self, &result)

guard status.isSuccess && (result == .unspecified || result == .proceed) else {
guard status.isSuccess && result.isSuccess else {
throw errorProducer(status, result)
}

return true
}

func apply(policy: SecPolicy) throws -> SecTrust {
Expand Down Expand Up @@ -463,9 +422,7 @@ public extension SecTrust {

/// The public keys contained in `self`.
var publicKeys: [SecKey] {
return (0..<SecTrustGetCertificateCount(self)).compactMap { index in
return SecTrustGetCertificateAtIndex(self, index)?.publicKey
}
return certificates.publicKeys
}

/// The `Data` values for all certificates contained in `self`.
Expand All @@ -478,6 +435,18 @@ public extension SecTrust {
SecTrustGetCertificateAtIndex(self, index)
}
}

func performDefaultEvaluation(forHost host: String) throws {
try validate(policy: .default) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, self, status, result)))
}
}

func validateHost(_ host: String) throws {
try validate(policy: .hostname(host)) { (status, result) in
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, self, status, result)))
}
}
}

extension SecPolicy {
Expand Down Expand Up @@ -522,3 +491,9 @@ extension SecCertificate {
extension OSStatus {
var isSuccess: Bool { return self == errSecSuccess }
}

extension SecTrustResultType {
var isSuccess: Bool {
return (self == .unspecified || self == .proceed)
}
}
6 changes: 1 addition & 5 deletions Source/SessionStateProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,7 @@ extension SessionDelegate: URLSessionTaskDelegate {
return (.performDefaultHandling, nil, nil)
}

guard try evaluator.evaluate(trust, forHost: host) else {
let error = AFError.serverTrustEvaluationFailed(reason: .unknown(host: host))

return (.cancelAuthenticationChallenge, nil, error)
}
try evaluator.evaluate(trust, forHost: host)

return (.useCredential, URLCredential(trust: trust), nil)
} catch {
Expand Down
Loading

0 comments on commit ae8fde4

Please sign in to comment.