服务商获取权限失败的话retry,更换了首页的背景图

This commit is contained in:
DDIsFriend
2023-11-21 13:47:33 +08:00
parent cfd5d93c71
commit 11d838906f
28 changed files with 921 additions and 372 deletions

View File

@@ -24,6 +24,10 @@
import Foundation
#if canImport(Security)
import Security
#endif
/// `AFError` is the error type returned by Alamofire. It encompasses a few different types of errors, each with
/// their own associated reasons.
public enum AFError: Error {
@@ -129,7 +133,7 @@ public enum AFError: Error {
case invalidEmptyResponse(type: String)
}
#if !(os(Linux) || os(Windows))
#if canImport(Security)
/// Underlying reason a server trust evaluation error occurred.
public enum ServerTrustFailureReason {
/// The output of a server trust evaluation.
@@ -211,7 +215,7 @@ public enum AFError: Error {
case responseValidationFailed(reason: ResponseValidationFailureReason)
/// Response serialization failed.
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
#if !(os(Linux) || os(Windows))
#if canImport(Security)
/// `ServerTrustEvaluating` instance threw an error during trust evaluation.
case serverTrustEvaluationFailed(reason: ServerTrustFailureReason)
#endif
@@ -314,7 +318,7 @@ extension AFError {
return false
}
#if !(os(Linux) || os(Windows))
#if canImport(Security)
/// Returns whether the instance is `.serverTrustEvaluationFailed`. When `true`, the `underlyingError` property will
/// contain the associated value.
public var isServerTrustEvaluationError: Bool {
@@ -393,7 +397,7 @@ extension AFError {
return reason.underlyingError
case let .responseSerializationFailed(reason):
return reason.underlyingError
#if !(os(Linux) || os(Windows))
#if canImport(Security)
case let .serverTrustEvaluationFailed(reason):
return reason.underlyingError
#endif
@@ -451,7 +455,7 @@ extension AFError {
return destination
}
#if !(os(Linux) || os(Windows))
#if canImport(Security)
/// The download resume data of any underlying network error. Only produced by `DownloadRequest`s.
public var downloadResumeData: Data? {
(underlyingError as? URLError)?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data
@@ -610,7 +614,7 @@ extension AFError.ResponseSerializationFailureReason {
}
}
#if !(os(Linux) || os(Windows))
#if canImport(Security)
extension AFError.ServerTrustFailureReason {
var output: AFError.ServerTrustFailureReason.Output? {
switch self {
@@ -688,7 +692,7 @@ extension AFError: LocalizedError {
"""
case let .sessionInvalidated(error):
return "Session was invalidated with error: \(error?.localizedDescription ?? "No description.")"
#if !(os(Linux) || os(Windows))
#if canImport(Security)
case let .serverTrustEvaluationFailed(reason):
return "Server trust evaluation failed due to reason: \(reason.localizedDescription)"
#endif
@@ -822,7 +826,7 @@ extension AFError.ResponseValidationFailureReason {
}
}
#if !(os(Linux) || os(Windows))
#if canImport(Security)
extension AFError.ServerTrustFailureReason {
var localizedDescription: String {
switch self {

View File

@@ -37,4 +37,4 @@ import Foundation
public let AF = Session.default
/// Current Alamofire version. Necessary since SPM doesn't use dynamic libraries. Plus this will be more accurate.
let version = "5.7.1"
let version = "5.8.0"

View File

@@ -217,15 +217,14 @@ public class AuthenticationInterceptor<AuthenticatorType>: RequestInterceptor wh
/// The `Credential` used to authenticate requests.
public var credential: Credential? {
get { $mutableState.credential }
set { $mutableState.credential = newValue }
get { mutableState.credential }
set { mutableState.credential = newValue }
}
let authenticator: AuthenticatorType
let queue = DispatchQueue(label: "org.alamofire.authentication.inspector")
@Protected
private var mutableState: MutableState
private let mutableState: Protected<MutableState>
// MARK: Initialization
@@ -242,13 +241,13 @@ public class AuthenticationInterceptor<AuthenticatorType>: RequestInterceptor wh
credential: Credential? = nil,
refreshWindow: RefreshWindow? = RefreshWindow()) {
self.authenticator = authenticator
mutableState = MutableState(credential: credential, refreshWindow: refreshWindow)
mutableState = Protected(MutableState(credential: credential, refreshWindow: refreshWindow))
}
// MARK: Adapt
public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
let adaptResult: AdaptResult = $mutableState.write { mutableState in
let adaptResult: AdaptResult = mutableState.write { mutableState in
// Queue the adapt operation if a refresh is already in place.
guard !mutableState.isRefreshing else {
let operation = AdaptOperation(urlRequest: urlRequest, session: session, completion: completion)
@@ -316,7 +315,7 @@ public class AuthenticationInterceptor<AuthenticatorType>: RequestInterceptor wh
return
}
$mutableState.write { mutableState in
mutableState.write { mutableState in
mutableState.requestsToRetry.append(completion)
guard !mutableState.isRefreshing else { return }
@@ -340,7 +339,7 @@ public class AuthenticationInterceptor<AuthenticatorType>: RequestInterceptor wh
// Dispatch to queue to hop out of the lock in case authenticator.refresh is implemented synchronously.
queue.async {
self.authenticator.refresh(credential, for: session) { result in
self.$mutableState.write { mutableState in
self.mutableState.write { mutableState in
switch result {
case let .success(credential):
self.handleRefreshSuccess(credential, insideLock: &mutableState)

View File

@@ -22,7 +22,7 @@
// THE SOFTWARE.
//
#if !((os(iOS) && (arch(i386) || arch(arm))) || os(Windows) || os(Linux))
#if !((os(iOS) && (arch(i386) || arch(arm))) || os(Windows) || os(Linux) || os(Android))
import Combine
import Dispatch
@@ -91,23 +91,22 @@ public struct DataResponsePublisher<Value>: Publisher {
where Downstream.Input == Output {
typealias Failure = Downstream.Failure
@Protected
private var downstream: Downstream?
private let downstream: Protected<Downstream?>
private let request: DataRequest
private let responseHandler: Handler
init(request: DataRequest, responseHandler: @escaping Handler, downstream: Downstream) {
self.request = request
self.responseHandler = responseHandler
self.downstream = downstream
self.downstream = Protected(downstream)
}
func request(_ demand: Subscribers.Demand) {
assert(demand > 0)
guard let downstream = downstream else { return }
guard let downstream = downstream.read({ $0 }) else { return }
self.downstream = nil
self.downstream.write(nil)
responseHandler { response in
_ = downstream.receive(response)
downstream.receive(completion: .finished)
@@ -116,7 +115,7 @@ public struct DataResponsePublisher<Value>: Publisher {
func cancel() {
request.cancel()
downstream = nil
downstream.write(nil)
}
}
}
@@ -312,23 +311,22 @@ public struct DataStreamPublisher<Value>: Publisher {
where Downstream.Input == Output {
typealias Failure = Downstream.Failure
@Protected
private var downstream: Downstream?
private let downstream: Protected<Downstream?>
private let request: DataStreamRequest
private let streamHandler: Handler
init(request: DataStreamRequest, streamHandler: @escaping Handler, downstream: Downstream) {
self.request = request
self.streamHandler = streamHandler
self.downstream = downstream
self.downstream = Protected(downstream)
}
func request(_ demand: Subscribers.Demand) {
assert(demand > 0)
guard let downstream = downstream else { return }
guard let downstream = downstream.read({ $0 }) else { return }
self.downstream = nil
self.downstream.write(nil)
streamHandler { stream in
_ = downstream.receive(stream)
if case .complete = stream.event {
@@ -339,7 +337,7 @@ public struct DataStreamPublisher<Value>: Publisher {
func cancel() {
request.cancel()
downstream = nil
downstream.write(nil)
}
}
}
@@ -462,23 +460,22 @@ public struct DownloadResponsePublisher<Value>: Publisher {
where Downstream.Input == Output {
typealias Failure = Downstream.Failure
@Protected
private var downstream: Downstream?
private let downstream: Protected<Downstream?>
private let request: DownloadRequest
private let responseHandler: Handler
init(request: DownloadRequest, responseHandler: @escaping Handler, downstream: Downstream) {
self.request = request
self.responseHandler = responseHandler
self.downstream = downstream
self.downstream = Protected(downstream)
}
func request(_ demand: Subscribers.Demand) {
assert(demand > 0)
guard let downstream = downstream else { return }
guard let downstream = downstream.read({ $0 }) else { return }
self.downstream = nil
self.downstream.write(nil)
responseHandler { response in
_ = downstream.receive(response)
downstream.receive(completion: .finished)
@@ -487,7 +484,7 @@ public struct DownloadResponsePublisher<Value>: Publisher {
func cancel() {
request.cancel()
downstream = nil
downstream.write(nil)
}
}
}

View File

@@ -37,7 +37,7 @@ extension Request {
/// - Returns: The `StreamOf<Progress>`.
public func uploadProgress(bufferingPolicy: StreamOf<Progress>.BufferingPolicy = .unbounded) -> StreamOf<Progress> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
uploadProgress(queue: .singleEventQueue) { progress in
uploadProgress(queue: underlyingQueue) { progress in
continuation.yield(progress)
}
}
@@ -50,7 +50,7 @@ extension Request {
/// - Returns: The `StreamOf<Progress>`.
public func downloadProgress(bufferingPolicy: StreamOf<Progress>.BufferingPolicy = .unbounded) -> StreamOf<Progress> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
downloadProgress(queue: .singleEventQueue) { progress in
downloadProgress(queue: underlyingQueue) { progress in
continuation.yield(progress)
}
}
@@ -63,7 +63,7 @@ extension Request {
/// - Returns: The `StreamOf<URLRequest>`.
public func urlRequests(bufferingPolicy: StreamOf<URLRequest>.BufferingPolicy = .unbounded) -> StreamOf<URLRequest> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
onURLRequestCreation(on: .singleEventQueue) { request in
onURLRequestCreation(on: underlyingQueue) { request in
continuation.yield(request)
}
}
@@ -76,7 +76,7 @@ extension Request {
/// - Returns: The `StreamOf<URLSessionTask>`.
public func urlSessionTasks(bufferingPolicy: StreamOf<URLSessionTask>.BufferingPolicy = .unbounded) -> StreamOf<URLSessionTask> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
onURLSessionTaskCreation(on: .singleEventQueue) { task in
onURLSessionTaskCreation(on: underlyingQueue) { task in
continuation.yield(task)
}
}
@@ -89,15 +89,15 @@ extension Request {
/// - Returns: The `StreamOf<String>`.
public func cURLDescriptions(bufferingPolicy: StreamOf<String>.BufferingPolicy = .unbounded) -> StreamOf<String> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
cURLDescription(on: .singleEventQueue) { description in
cURLDescription(on: underlyingQueue) { description in
continuation.yield(description)
}
}
}
private func stream<T>(of type: T.Type = T.self,
bufferingPolicy: StreamOf<T>.BufferingPolicy = .unbounded,
yielder: @escaping (StreamOf<T>.Continuation) -> Void) -> StreamOf<T> {
fileprivate func stream<T>(of type: T.Type = T.self,
bufferingPolicy: StreamOf<T>.BufferingPolicy = .unbounded,
yielder: @escaping (StreamOf<T>.Continuation) -> Void) -> StreamOf<T> {
StreamOf<T>(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
yielder(continuation)
// Must come after serializers run in order to catch retry progress.
@@ -168,18 +168,83 @@ public struct DataTask<Value> {
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension DataRequest {
/// Creates a `StreamOf<HTTPURLResponse>` for the instance's responses.
///
/// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default.
///
/// - Returns: The `StreamOf<HTTPURLResponse>`.
public func httpResponses(bufferingPolicy: StreamOf<HTTPURLResponse>.BufferingPolicy = .unbounded) -> StreamOf<HTTPURLResponse> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
onHTTPResponse(on: underlyingQueue) { response in
continuation.yield(response)
}
}
}
#if swift(>=5.7)
/// Sets an async closure returning a `Request.ResponseDisposition`, called whenever the `DataRequest` produces an
/// `HTTPURLResponse`.
///
/// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries).
/// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams,
/// where responses after the first will contain the part headers.
///
/// - Parameters:
/// - handler: Async closure executed when a new `HTTPURLResponse` is received and returning a
/// `ResponseDisposition` value. This value determines whether to continue the request or cancel it as
/// if `cancel()` had been called on the instance. Note, this closure is called on an arbitrary thread,
/// so any synchronous calls in it will execute in that context.
///
/// - Returns: The instance.
@_disfavoredOverload
@discardableResult
public func onHTTPResponse(
perform handler: @escaping @Sendable (_ response: HTTPURLResponse) async -> ResponseDisposition
) -> Self {
onHTTPResponse(on: underlyingQueue) { response, completionHandler in
Task {
let disposition = await handler(response)
completionHandler(disposition)
}
}
return self
}
/// Sets an async closure called whenever the `DataRequest` produces an `HTTPURLResponse`.
///
/// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries).
/// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams,
/// where responses after the first will contain the part headers.
///
/// - Parameters:
/// - handler: Async closure executed when a new `HTTPURLResponse` is received. Note, this closure is called on an
/// arbitrary thread, so any synchronous calls in it will execute in that context.
///
/// - Returns: The instance.
@discardableResult
public func onHTTPResponse(perform handler: @escaping @Sendable (_ response: HTTPURLResponse) async -> Void) -> Self {
onHTTPResponse { response in
await handler(response)
return .allow
}
return self
}
#endif
/// Creates a `DataTask` to `await` a `Data` value.
///
/// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion.
/// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default.
/// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default.
///
/// - Returns: The `DataTask`.
public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = false,
public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods) -> DataTask<Data> {
@@ -195,7 +260,7 @@ extension DataRequest {
/// - type: `Decodable` type to decode from response data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer.
/// `PassthroughPreprocessor()` by default.
/// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default.
@@ -204,7 +269,7 @@ extension DataRequest {
///
/// - Returns: The `DataTask`.
public func serializingDecodable<Value: Decodable>(_ type: Value.Type = Value.self,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false,
automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<Value>.defaultDataPreprocessor,
decoder: DataDecoder = JSONDecoder(),
emptyResponseCodes: Set<Int> = DecodableResponseSerializer<Value>.defaultEmptyResponseCodes,
@@ -221,7 +286,7 @@ extension DataRequest {
/// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer.
/// `PassthroughPreprocessor()` by default.
/// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case
@@ -231,7 +296,7 @@ extension DataRequest {
/// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default.
///
/// - Returns: The `DataTask`.
public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = false,
public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
encoding: String.Encoding? = nil,
emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
@@ -249,16 +314,16 @@ extension DataRequest {
/// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
///
/// - Returns: The `DataTask`.
public func serializingResponse<Serializer: ResponseSerializer>(using serializer: Serializer,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false)
automaticallyCancelling shouldAutomaticallyCancel: Bool = true)
-> DataTask<Serializer.SerializedObject> {
dataTask(automaticallyCancelling: shouldAutomaticallyCancel) {
self.response(queue: .singleEventQueue,
responseSerializer: serializer,
completionHandler: $0)
dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in
response(queue: underlyingQueue,
responseSerializer: serializer,
completionHandler: $0)
}
}
@@ -269,16 +334,16 @@ extension DataRequest {
/// response, and data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DataTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
///
/// - Returns: The `DataTask`.
public func serializingResponse<Serializer: DataResponseSerializerProtocol>(using serializer: Serializer,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false)
automaticallyCancelling shouldAutomaticallyCancel: Bool = true)
-> DataTask<Serializer.SerializedObject> {
dataTask(automaticallyCancelling: shouldAutomaticallyCancel) {
self.response(queue: .singleEventQueue,
responseSerializer: serializer,
completionHandler: $0)
dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in
response(queue: underlyingQueue,
responseSerializer: serializer,
completionHandler: $0)
}
}
@@ -366,13 +431,13 @@ extension DownloadRequest {
/// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion.
/// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default.
/// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default.
///
/// - Returns: The `DownloadTask`.
public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = false,
public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods) -> DownloadTask<Data> {
@@ -390,7 +455,7 @@ extension DownloadRequest {
/// - type: `Decodable` type to decode from response data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer.
/// `PassthroughPreprocessor()` by default.
/// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default.
@@ -399,7 +464,7 @@ extension DownloadRequest {
///
/// - Returns: The `DownloadTask`.
public func serializingDecodable<Value: Decodable>(_ type: Value.Type = Value.self,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false,
automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<Value>.defaultDataPreprocessor,
decoder: DataDecoder = JSONDecoder(),
emptyResponseCodes: Set<Int> = DecodableResponseSerializer<Value>.defaultEmptyResponseCodes,
@@ -416,10 +481,10 @@ extension DownloadRequest {
/// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
///
/// - Returns: The `DownloadTask`.
public func serializingDownloadedFileURL(automaticallyCancelling shouldAutomaticallyCancel: Bool = false) -> DownloadTask<URL> {
public func serializingDownloadedFileURL(automaticallyCancelling shouldAutomaticallyCancel: Bool = true) -> DownloadTask<URL> {
serializingDownload(using: URLResponseSerializer(),
automaticallyCancelling: shouldAutomaticallyCancel)
}
@@ -429,7 +494,7 @@ extension DownloadRequest {
/// - Parameters:
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
/// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the
/// serializer. `PassthroughPreprocessor()` by default.
/// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case
@@ -439,7 +504,7 @@ extension DownloadRequest {
/// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default.
///
/// - Returns: The `DownloadTask`.
public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = false,
public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = true,
dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
encoding: String.Encoding? = nil,
emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
@@ -457,16 +522,16 @@ extension DownloadRequest {
/// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
///
/// - Returns: The `DownloadTask`.
public func serializingDownload<Serializer: ResponseSerializer>(using serializer: Serializer,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false)
automaticallyCancelling shouldAutomaticallyCancel: Bool = true)
-> DownloadTask<Serializer.SerializedObject> {
downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) {
self.response(queue: .singleEventQueue,
responseSerializer: serializer,
completionHandler: $0)
downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in
response(queue: underlyingQueue,
responseSerializer: serializer,
completionHandler: $0)
}
}
@@ -478,16 +543,16 @@ extension DownloadRequest {
/// response, and data.
/// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the
/// enclosing async context is cancelled. Only applies to `DownloadTask`'s async
/// properties. `false` by default.
/// properties. `true` by default.
///
/// - Returns: The `DownloadTask`.
public func serializingDownload<Serializer: DownloadResponseSerializerProtocol>(using serializer: Serializer,
automaticallyCancelling shouldAutomaticallyCancel: Bool = false)
automaticallyCancelling shouldAutomaticallyCancel: Bool = true)
-> DownloadTask<Serializer.SerializedObject> {
downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) {
self.response(queue: .singleEventQueue,
responseSerializer: serializer,
completionHandler: $0)
downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in
response(queue: underlyingQueue,
responseSerializer: serializer,
completionHandler: $0)
}
}
@@ -625,6 +690,69 @@ public struct DataStreamTask {
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension DataStreamRequest {
/// Creates a `StreamOf<HTTPURLResponse>` for the instance's responses.
///
/// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default.
///
/// - Returns: The `StreamOf<HTTPURLResponse>`.
public func httpResponses(bufferingPolicy: StreamOf<HTTPURLResponse>.BufferingPolicy = .unbounded) -> StreamOf<HTTPURLResponse> {
stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in
onHTTPResponse(on: underlyingQueue) { response in
continuation.yield(response)
}
}
}
#if swift(>=5.7)
/// Sets an async closure returning a `Request.ResponseDisposition`, called whenever the `DataStreamRequest`
/// produces an `HTTPURLResponse`.
///
/// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries).
/// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams,
/// where responses after the first will contain the part headers.
///
/// - Parameters:
/// - handler: Async closure executed when a new `HTTPURLResponse` is received and returning a
/// `ResponseDisposition` value. This value determines whether to continue the request or cancel it as
/// if `cancel()` had been called on the instance. Note, this closure is called on an arbitrary thread,
/// so any synchronous calls in it will execute in that context.
///
/// - Returns: The instance.
@_disfavoredOverload
@discardableResult
public func onHTTPResponse(perform handler: @escaping @Sendable (HTTPURLResponse) async -> ResponseDisposition) -> Self {
onHTTPResponse(on: underlyingQueue) { response, completionHandler in
Task {
let disposition = await handler(response)
completionHandler(disposition)
}
}
return self
}
/// Sets an async closure called whenever the `DataStreamRequest` produces an `HTTPURLResponse`.
///
/// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries).
/// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams,
/// where responses after the first will contain the part headers.
///
/// - Parameters:
/// - handler: Async closure executed when a new `HTTPURLResponse` is received. Note, this closure is called on an
/// arbitrary thread, so any synchronous calls in it will execute in that context.
///
/// - Returns: The instance.
@discardableResult
public func onHTTPResponse(perform handler: @escaping @Sendable (HTTPURLResponse) async -> Void) -> Self {
onHTTPResponse { response in
await handler(response)
return .allow
}
return self
}
#endif
/// Creates a `DataStreamTask` used to `await` streams of serialized values.
///
/// - Returns: The `DataStreamTask`.

View File

@@ -69,6 +69,9 @@ public protocol EventMonitor {
// MARK: URLSessionDataDelegate Events
/// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:didReceive:completionHandler:)` method.
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse)
/// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:didReceive:)` method.
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)
@@ -244,6 +247,7 @@ extension EventMonitor {
didFinishCollecting metrics: URLSessionTaskMetrics) {}
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {}
public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) {}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {}
public func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
@@ -380,6 +384,10 @@ public final class CompositeEventMonitor: EventMonitor {
performEvent { $0.urlSession(session, taskIsWaitingForConnectivity: task) }
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) {
performEvent { $0.urlSession(session, dataTask: dataTask, didReceive: response) }
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
performEvent { $0.urlSession(session, dataTask: dataTask, didReceive: data) }
}
@@ -593,6 +601,9 @@ open class ClosureEventMonitor: EventMonitor {
/// Closure called on the `urlSession(_:taskIsWaitingForConnectivity:)` event.
open var taskIsWaitingForConnectivity: ((URLSession, URLSessionTask) -> Void)?
/// Closure called on the `urlSession(_:dataTask:didReceive:completionHandler:)` event.
open var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> Void)?
/// Closure that receives the `urlSession(_:dataTask:didReceive:)` event.
open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
@@ -741,6 +752,10 @@ open class ClosureEventMonitor: EventMonitor {
taskIsWaitingForConnectivity?(session, task)
}
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) {
dataTaskDidReceiveResponse?(session, dataTask, response)
}
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
dataTaskDidReceiveData?(session, dataTask, data)
}

View File

@@ -34,16 +34,12 @@ public struct HTTPHeaders {
/// Creates an instance from an array of `HTTPHeader`s. Duplicate case-insensitive names are collapsed into the last
/// name and value encountered.
public init(_ headers: [HTTPHeader]) {
self.init()
headers.forEach { update($0) }
}
/// Creates an instance from a `[String: String]`. Duplicate case-insensitive names are collapsed into the last name
/// and value encountered.
public init(_ dictionary: [String: String]) {
self.init()
dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) }
}
@@ -145,8 +141,6 @@ public struct HTTPHeaders {
extension HTTPHeaders: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, String)...) {
self.init()
elements.forEach { update(name: $0.0, value: $0.1) }
}
}
@@ -405,6 +399,8 @@ extension HTTPHeader {
return "Linux"
#elseif os(Windows)
return "Windows"
#elseif os(Android)
return "Android"
#else
return "Unknown"
#endif

View File

@@ -24,9 +24,9 @@
import Foundation
#if os(iOS) || os(watchOS) || os(tvOS)
#if canImport(MobileCoreServices)
import MobileCoreServices
#elseif os(macOS)
#elseif canImport(CoreServices)
import CoreServices
#endif
@@ -213,7 +213,7 @@ open class MultipartFormData {
// Check 2 - is file URL reachable?
//============================================================
#if !(os(Linux) || os(Windows))
#if !(os(Linux) || os(Windows) || os(Android))
do {
let isReachable = try fileURL.checkPromisedItemIsReachable()
guard isReachable else {
@@ -455,9 +455,11 @@ open class MultipartFormData {
inputStream.open()
defer { inputStream.close() }
while inputStream.hasBytesAvailable {
var buffer = [UInt8](repeating: 0, count: streamBufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)
var bytesLeftToRead = bodyPart.bodyContentLength
while inputStream.hasBytesAvailable && bytesLeftToRead > 0 {
let bufferSize = min(streamBufferSize, Int(bytesLeftToRead))
var buffer = [UInt8](repeating: 0, count: bufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: bufferSize)
if let streamError = inputStream.streamError {
throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError))
@@ -469,6 +471,7 @@ open class MultipartFormData {
}
try write(&buffer, to: outputStream)
bytesLeftToRead -= UInt64(bytesRead)
} else {
break
}
@@ -549,6 +552,19 @@ extension MultipartFormData {
// MARK: - Private - Mime Type
private func mimeType(forPathExtension pathExtension: String) -> String {
#if swift(>=5.9)
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, visionOS 1, *) {
return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream"
} else {
if
let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() {
return contentType as String
}
return "application/octet-stream"
}
#else
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) {
return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream"
} else {
@@ -560,6 +576,7 @@ extension MultipartFormData {
return "application/octet-stream"
}
#endif
}
}
@@ -569,7 +586,7 @@ extension MultipartFormData {
// MARK: - Private - Mime Type
private func mimeType(forPathExtension pathExtension: String) -> String {
#if !(os(Linux) || os(Windows))
#if canImport(CoreServices) || canImport(MobileCoreServices)
if
let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() {

View File

@@ -28,8 +28,8 @@ import Foundation
final class MultipartUpload {
lazy var result = Result { try build() }
@Protected
private(set) var multipartFormData: MultipartFormData
private let multipartFormData: Protected<MultipartFormData>
let encodingMemoryThreshold: UInt64
let request: URLRequestConvertible
let fileManager: FileManager
@@ -40,13 +40,13 @@ final class MultipartUpload {
self.encodingMemoryThreshold = encodingMemoryThreshold
self.request = request
fileManager = multipartFormData.fileManager
self.multipartFormData = multipartFormData
self.multipartFormData = Protected(multipartFormData)
}
func build() throws -> UploadRequest.Uploadable {
let uploadable: UploadRequest.Uploadable
if $multipartFormData.contentLength < encodingMemoryThreshold {
let data = try $multipartFormData.read { try $0.encode() }
if multipartFormData.contentLength < encodingMemoryThreshold {
let data = try multipartFormData.read { try $0.encode() }
uploadable = .data(data)
} else {
@@ -58,7 +58,7 @@ final class MultipartUpload {
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
do {
try $multipartFormData.read { try $0.writeEncodedData(to: fileURL) }
try multipartFormData.read { try $0.writeEncodedData(to: fileURL) }
} catch {
// Cleanup after attempted write if it fails.
try? fileManager.removeItem(at: fileURL)
@@ -76,7 +76,7 @@ extension MultipartUpload: UploadConvertible {
func asURLRequest() throws -> URLRequest {
var urlRequest = try request.asURLRequest()
$multipartFormData.read { multipartFormData in
multipartFormData.read { multipartFormData in
urlRequest.headers.add(.contentType(multipartFormData.contentType))
}

View File

@@ -22,7 +22,7 @@
// THE SOFTWARE.
//
#if !(os(watchOS) || os(Linux) || os(Windows))
#if canImport(SystemConfiguration)
import Foundation
import SystemConfiguration
@@ -113,8 +113,7 @@ open class NetworkReachabilityManager {
private let reachability: SCNetworkReachability
/// Protected storage for mutable state.
@Protected
private var mutableState = MutableState()
private let mutableState = Protected(MutableState())
// MARK: - Initialization
@@ -168,7 +167,7 @@ open class NetworkReachabilityManager {
onUpdatePerforming listener: @escaping Listener) -> Bool {
stopListening()
$mutableState.write { state in
mutableState.write { state in
state.listenerQueue = queue
state.listener = listener
}
@@ -194,7 +193,8 @@ open class NetworkReachabilityManager {
let description = weakManager.manager?.flags?.readableDescription ?? "nil"
return Unmanaged.passRetained(description as CFString)
})
}
)
let callback: SCNetworkReachabilityCallBack = { _, flags, info in
guard let info = info else { return }
@@ -219,7 +219,7 @@ open class NetworkReachabilityManager {
open func stopListening() {
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
$mutableState.write { state in
mutableState.write { state in
state.listener = nil
state.listenerQueue = nil
state.previousStatus = nil
@@ -236,7 +236,7 @@ open class NetworkReachabilityManager {
func notifyListener(_ flags: SCNetworkReachabilityFlags) {
let newStatus = NetworkReachabilityStatus(flags)
$mutableState.write { state in
mutableState.write { state in
guard state.previousStatus != newStatus else { return }
state.previousStatus = newStatus
@@ -266,7 +266,7 @@ extension SCNetworkReachabilityFlags {
var canConnectWithoutUserInteraction: Bool { canConnectAutomatically && !contains(.interventionRequired) }
var isActuallyReachable: Bool { isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) }
var isCellular: Bool {
#if os(iOS) || os(tvOS)
#if os(iOS) || os(tvOS) || (swift(>=5.9) && os(visionOS))
return contains(.isWWAN)
#else
return false

View File

@@ -49,13 +49,7 @@ extension Lock {
}
}
#if os(Linux) || os(Windows)
extension NSLock: Lock {}
#endif
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
#if canImport(Darwin)
/// An `os_unfair_lock` wrapper.
final class UnfairLock: Lock {
private let unfairLock: os_unfair_lock_t
@@ -78,41 +72,35 @@ final class UnfairLock: Lock {
os_unfair_lock_unlock(unfairLock)
}
}
#elseif canImport(Foundation)
extension NSLock: Lock {}
#else
#error("This platform needs a Lock-conforming type without Foundation.")
#endif
/// A thread-safe wrapper around a value.
@propertyWrapper
@dynamicMemberLookup
final class Protected<T> {
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
final class Protected<Value> {
#if canImport(Darwin)
private let lock = UnfairLock()
#elseif os(Linux) || os(Windows)
#elseif canImport(Foundation)
private let lock = NSLock()
#else
#error("This platform needs a Lock-conforming type without Foundation.")
#endif
private var value: T
private var value: Value
init(_ value: T) {
init(_ value: Value) {
self.value = value
}
/// The contained value. Unsafe for anything more than direct read or write.
var wrappedValue: T {
get { lock.around { value } }
set { lock.around { value = newValue } }
}
var projectedValue: Protected<T> { self }
init(wrappedValue: T) {
value = wrappedValue
}
/// Synchronously read or transform the contained value.
///
/// - Parameter closure: The closure to execute.
///
/// - Returns: The return value of the closure passed.
func read<U>(_ closure: (T) throws -> U) rethrows -> U {
func read<U>(_ closure: (Value) throws -> U) rethrows -> U {
try lock.around { try closure(self.value) }
}
@@ -122,21 +110,28 @@ final class Protected<T> {
///
/// - Returns: The modified value.
@discardableResult
func write<U>(_ closure: (inout T) throws -> U) rethrows -> U {
func write<U>(_ closure: (inout Value) throws -> U) rethrows -> U {
try lock.around { try closure(&self.value) }
}
subscript<Property>(dynamicMember keyPath: WritableKeyPath<T, Property>) -> Property {
/// Synchronously update the protected value.
///
/// - Parameter value: The `Value`.
func write(_ value: Value) {
write { $0 = value }
}
subscript<Property>(dynamicMember keyPath: WritableKeyPath<Value, Property>) -> Property {
get { lock.around { value[keyPath: keyPath] } }
set { lock.around { value[keyPath: keyPath] = newValue } }
}
subscript<Property>(dynamicMember keyPath: KeyPath<T, Property>) -> Property {
subscript<Property>(dynamicMember keyPath: KeyPath<Value, Property>) -> Property {
lock.around { value[keyPath: keyPath] }
}
}
extension Protected where T == Request.MutableState {
extension Protected where Value == Request.MutableState {
/// Attempts to transition to the passed `State`.
///
/// - Parameter state: The `State` to attempt transition to.
@@ -159,3 +154,15 @@ extension Protected where T == Request.MutableState {
lock.around { perform(value.state) }
}
}
extension Protected: Equatable where Value: Equatable {
static func ==(lhs: Protected<Value>, rhs: Protected<Value>) -> Bool {
lhs.read { left in rhs.read { right in left == right }}
}
}
extension Protected: Hashable where Value: Hashable {
func hash(into hasher: inout Hasher) {
read { hasher.combine($0) }
}
}

View File

@@ -125,11 +125,10 @@ public class Request {
}
/// Protected `MutableState` value that provides thread-safe access to state values.
@Protected
fileprivate var mutableState = MutableState()
fileprivate let mutableState = Protected(MutableState())
/// `State` of the `Request`.
public var state: State { $mutableState.state }
public var state: State { mutableState.state }
/// Returns whether `state` is `.initialized`.
public var isInitialized: Bool { state == .initialized }
/// Returns whether `state is `.resumed`.
@@ -152,50 +151,49 @@ public class Request {
public let downloadProgress = Progress(totalUnitCount: 0)
/// `ProgressHandler` called when `uploadProgress` is updated, on the provided `DispatchQueue`.
private var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? {
get { $mutableState.uploadProgressHandler }
set { $mutableState.uploadProgressHandler = newValue }
get { mutableState.uploadProgressHandler }
set { mutableState.uploadProgressHandler = newValue }
}
/// `ProgressHandler` called when `downloadProgress` is updated, on the provided `DispatchQueue`.
fileprivate var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? {
get { $mutableState.downloadProgressHandler }
set { $mutableState.downloadProgressHandler = newValue }
get { mutableState.downloadProgressHandler }
set { mutableState.downloadProgressHandler = newValue }
}
// MARK: Redirect Handling
/// `RedirectHandler` set on the instance.
public private(set) var redirectHandler: RedirectHandler? {
get { $mutableState.redirectHandler }
set { $mutableState.redirectHandler = newValue }
get { mutableState.redirectHandler }
set { mutableState.redirectHandler = newValue }
}
// MARK: Cached Response Handling
/// `CachedResponseHandler` set on the instance.
public private(set) var cachedResponseHandler: CachedResponseHandler? {
get { $mutableState.cachedResponseHandler }
set { $mutableState.cachedResponseHandler = newValue }
get { mutableState.cachedResponseHandler }
set { mutableState.cachedResponseHandler = newValue }
}
// MARK: URLCredential
/// `URLCredential` used for authentication challenges. Created by calling one of the `authenticate` methods.
public private(set) var credential: URLCredential? {
get { $mutableState.credential }
set { $mutableState.credential = newValue }
get { mutableState.credential }
set { mutableState.credential = newValue }
}
// MARK: Validators
/// `Validator` callback closures that store the validation calls enqueued.
@Protected
fileprivate var validators: [() -> Void] = []
fileprivate let validators = Protected<[() -> Void]>([])
// MARK: URLRequests
/// All `URLRequests` created on behalf of the `Request`, including original and adapted requests.
public var requests: [URLRequest] { $mutableState.requests }
public var requests: [URLRequest] { mutableState.requests }
/// First `URLRequest` created on behalf of the `Request`. May not be the first one actually executed.
public var firstRequest: URLRequest? { requests.first }
/// Last `URLRequest` created on behalf of the `Request`.
@@ -205,7 +203,7 @@ public class Request {
/// `URLRequest`s from all of the `URLSessionTask`s executed on behalf of the `Request`. May be different from
/// `requests` due to `URLSession` manipulation.
public var performedRequests: [URLRequest] { $mutableState.read { $0.tasks.compactMap(\.currentRequest) } }
public var performedRequests: [URLRequest] { mutableState.read { $0.tasks.compactMap(\.currentRequest) } }
// MARK: HTTPURLResponse
@@ -216,7 +214,7 @@ public class Request {
// MARK: Tasks
/// All `URLSessionTask`s created on behalf of the `Request`.
public var tasks: [URLSessionTask] { $mutableState.tasks }
public var tasks: [URLSessionTask] { mutableState.tasks }
/// First `URLSessionTask` created on behalf of the `Request`.
public var firstTask: URLSessionTask? { tasks.first }
/// Last `URLSessionTask` created on behalf of the `Request`.
@@ -227,7 +225,7 @@ public class Request {
// MARK: Metrics
/// All `URLSessionTaskMetrics` gathered on behalf of the `Request`. Should correspond to the `tasks` created.
public var allMetrics: [URLSessionTaskMetrics] { $mutableState.metrics }
public var allMetrics: [URLSessionTaskMetrics] { mutableState.metrics }
/// First `URLSessionTaskMetrics` gathered on behalf of the `Request`.
public var firstMetrics: URLSessionTaskMetrics? { allMetrics.first }
/// Last `URLSessionTaskMetrics` gathered on behalf of the `Request`.
@@ -238,14 +236,14 @@ public class Request {
// MARK: Retry Count
/// Number of times the `Request` has been retried.
public var retryCount: Int { $mutableState.retryCount }
public var retryCount: Int { mutableState.retryCount }
// MARK: Error
/// `Error` returned from Alamofire internally, from the network request directly, or any validators executed.
public fileprivate(set) var error: AFError? {
get { $mutableState.error }
set { $mutableState.error = newValue }
get { mutableState.error }
set { mutableState.error = newValue }
}
/// Default initializer for the `Request` superclass.
@@ -283,7 +281,7 @@ public class Request {
func didCreateInitialURLRequest(_ request: URLRequest) {
dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { $0.requests.append(request) }
mutableState.write { $0.requests.append(request) }
eventMonitor?.request(self, didCreateInitialURLRequest: request)
}
@@ -313,7 +311,7 @@ public class Request {
func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) {
dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { $0.requests.append(adaptedRequest) }
mutableState.write { $0.requests.append(adaptedRequest) }
eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest)
}
@@ -343,7 +341,7 @@ public class Request {
func didCreateURLRequest(_ request: URLRequest) {
dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.read { state in
mutableState.read { state in
state.urlRequestHandler?.queue.async { state.urlRequestHandler?.handler(request) }
}
@@ -354,7 +352,7 @@ public class Request {
/// Asynchronously calls any stored `cURLHandler` and then removes it from `mutableState`.
private func callCURLHandlerIfNecessary() {
$mutableState.write { mutableState in
mutableState.write { mutableState in
guard let cURLHandler = mutableState.cURLHandler else { return }
cURLHandler.queue.async { cURLHandler.handler(self.cURLDescription()) }
@@ -369,7 +367,7 @@ public class Request {
func didCreateTask(_ task: URLSessionTask) {
dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { state in
mutableState.write { state in
state.tasks.append(task)
guard let urlSessionTaskHandler = state.urlSessionTaskHandler else { return }
@@ -416,7 +414,9 @@ public class Request {
func didCancel() {
dispatchPrecondition(condition: .onQueue(underlyingQueue))
error = error ?? AFError.explicitlyCancelled
mutableState.write { mutableState in
mutableState.error = mutableState.error ?? AFError.explicitlyCancelled
}
eventMonitor?.requestDidCancel(self)
}
@@ -436,7 +436,7 @@ public class Request {
func didGatherMetrics(_ metrics: URLSessionTaskMetrics) {
dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { $0.metrics.append(metrics) }
mutableState.write { $0.metrics.append(metrics) }
eventMonitor?.request(self, didGatherMetrics: metrics)
}
@@ -468,6 +468,7 @@ public class Request {
self.error = self.error ?? error
let validators = validators.read { $0 }
validators.forEach { $0() }
eventMonitor?.request(self, didCompleteTask: task, with: error)
@@ -479,7 +480,7 @@ public class Request {
func prepareForRetry() {
dispatchPrecondition(condition: .onQueue(underlyingQueue))
$mutableState.write { $0.retryCount += 1 }
mutableState.write { $0.retryCount += 1 }
reset()
@@ -513,9 +514,9 @@ public class Request {
func finish(error: AFError? = nil) {
dispatchPrecondition(condition: .onQueue(underlyingQueue))
guard !$mutableState.isFinishing else { return }
guard !mutableState.isFinishing else { return }
$mutableState.isFinishing = true
mutableState.isFinishing = true
if let error = error { self.error = error }
@@ -531,7 +532,7 @@ public class Request {
///
/// - Parameter closure: The closure containing the response serialization call.
func appendResponseSerializer(_ closure: @escaping () -> Void) {
$mutableState.write { mutableState in
mutableState.write { mutableState in
mutableState.responseSerializers.append(closure)
if mutableState.state == .finished {
@@ -554,7 +555,7 @@ public class Request {
func nextResponseSerializer() -> (() -> Void)? {
var responseSerializer: (() -> Void)?
$mutableState.write { mutableState in
mutableState.write { mutableState in
let responseSerializerIndex = mutableState.responseSerializerCompletions.count
if responseSerializerIndex < mutableState.responseSerializers.count {
@@ -571,7 +572,7 @@ public class Request {
// Execute all response serializer completions and clear them
var completions: [() -> Void] = []
$mutableState.write { mutableState in
mutableState.write { mutableState in
completions = mutableState.responseSerializerCompletions
// Clear out all response serializers and response serializer completions in mutable state since the
@@ -605,7 +606,7 @@ public class Request {
/// - Parameter completion: The completion handler provided with the response serializer, called when all serializers
/// are complete.
func responseSerializerDidComplete(completion: @escaping () -> Void) {
$mutableState.write { $0.responseSerializerCompletions.append(completion) }
mutableState.write { $0.responseSerializerCompletions.append(completion) }
processNextResponseSerializer()
}
@@ -618,7 +619,7 @@ public class Request {
downloadProgress.totalUnitCount = 0
downloadProgress.completedUnitCount = 0
$mutableState.write { state in
mutableState.write { state in
state.isFinishing = false
state.responseSerializerCompletions = []
}
@@ -640,7 +641,7 @@ public class Request {
///
/// - Parameter perform: The closure to perform.
func withState(perform: (State) -> Void) {
$mutableState.withState(perform: perform)
mutableState.withState(perform: perform)
}
// MARK: Task Creation
@@ -667,7 +668,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func cancel() -> Self {
$mutableState.write { mutableState in
mutableState.write { mutableState in
guard mutableState.state.canTransitionTo(.cancelled) else { return }
mutableState.state = .cancelled
@@ -693,7 +694,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func suspend() -> Self {
$mutableState.write { mutableState in
mutableState.write { mutableState in
guard mutableState.state.canTransitionTo(.suspended) else { return }
mutableState.state = .suspended
@@ -714,7 +715,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func resume() -> Self {
$mutableState.write { mutableState in
mutableState.write { mutableState in
guard mutableState.state.canTransitionTo(.resumed) else { return }
mutableState.state = .resumed
@@ -754,7 +755,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func authenticate(with credential: URLCredential) -> Self {
$mutableState.credential = credential
mutableState.credential = credential
return self
}
@@ -770,7 +771,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func downloadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self {
$mutableState.downloadProgressHandler = (handler: closure, queue: queue)
mutableState.downloadProgressHandler = (handler: closure, queue: queue)
return self
}
@@ -786,7 +787,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func uploadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self {
$mutableState.uploadProgressHandler = (handler: closure, queue: queue)
mutableState.uploadProgressHandler = (handler: closure, queue: queue)
return self
}
@@ -802,7 +803,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func redirect(using handler: RedirectHandler) -> Self {
$mutableState.write { mutableState in
mutableState.write { mutableState in
precondition(mutableState.redirectHandler == nil, "Redirect handler has already been set.")
mutableState.redirectHandler = handler
}
@@ -821,7 +822,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func cacheResponse(using handler: CachedResponseHandler) -> Self {
$mutableState.write { mutableState in
mutableState.write { mutableState in
precondition(mutableState.cachedResponseHandler == nil, "Cached response handler has already been set.")
mutableState.cachedResponseHandler = handler
}
@@ -842,7 +843,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func cURLDescription(on queue: DispatchQueue, calling handler: @escaping (String) -> Void) -> Self {
$mutableState.write { mutableState in
mutableState.write { mutableState in
if mutableState.requests.last != nil {
queue.async { handler(self.cURLDescription()) }
} else {
@@ -863,13 +864,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func cURLDescription(calling handler: @escaping (String) -> Void) -> Self {
$mutableState.write { mutableState in
if mutableState.requests.last != nil {
underlyingQueue.async { handler(self.cURLDescription()) }
} else {
mutableState.cURLHandler = (underlyingQueue, handler)
}
}
cURLDescription(on: underlyingQueue, calling: handler)
return self
}
@@ -885,7 +880,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func onURLRequestCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLRequest) -> Void) -> Self {
$mutableState.write { state in
mutableState.write { state in
if let request = state.requests.last {
queue.async { handler(request) }
}
@@ -909,7 +904,7 @@ public class Request {
/// - Returns: The instance.
@discardableResult
public func onURLSessionTaskCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLSessionTask) -> Void) -> Self {
$mutableState.write { state in
mutableState.write { state in
if let task = state.tasks.last {
queue.async { handler(task) }
}
@@ -928,19 +923,37 @@ public class Request {
func onFinish(perform finishHandler: @escaping () -> Void) {
guard !isFinished else { finishHandler(); return }
$mutableState.write { state in
mutableState.write { state in
state.finishHandlers.append(finishHandler)
}
}
/// Final cleanup step executed when the instance finishes response serialization.
func cleanup() {
delegate?.cleanup(after: self)
let handlers = $mutableState.finishHandlers
let handlers = mutableState.finishHandlers
handlers.forEach { $0() }
$mutableState.write { state in
mutableState.write { state in
state.finishHandlers.removeAll()
}
delegate?.cleanup(after: self)
}
}
extension Request {
/// Type indicating how a `DataRequest` or `DataStreamRequest` should proceed after receiving an `HTTPURLResponse`.
public enum ResponseDisposition {
/// Allow the request to continue normally.
case allow
/// Cancel the request, similar to calling `cancel()`.
case cancel
var sessionDisposition: URLSession.ResponseDisposition {
switch self {
case .allow: return .allow
case .cancel: return .cancel
}
}
}
}
@@ -1085,11 +1098,16 @@ public class DataRequest: Request {
/// `URLRequestConvertible` value used to create `URLRequest`s for this instance.
public let convertible: URLRequestConvertible
/// `Data` read from the server so far.
public var data: Data? { mutableData }
public var data: Data? { dataMutableState.data }
/// Protected storage for the `Data` read by the instance.
@Protected
private var mutableData: Data? = nil
private struct DataMutableState {
var data: Data?
var httpResponseHandler: (queue: DispatchQueue,
handler: (_ response: HTTPURLResponse,
_ completionHandler: @escaping (ResponseDisposition) -> Void) -> Void)?
}
private let dataMutableState = Protected(DataMutableState())
/// Creates a `DataRequest` using the provided parameters.
///
@@ -1122,7 +1140,9 @@ public class DataRequest: Request {
override func reset() {
super.reset()
mutableData = nil
dataMutableState.write { mutableState in
mutableState.data = nil
}
}
/// Called when `Data` is received by this instance.
@@ -1131,15 +1151,41 @@ public class DataRequest: Request {
///
/// - Parameter data: The `Data` received.
func didReceive(data: Data) {
if self.data == nil {
mutableData = data
} else {
$mutableData.write { $0?.append(data) }
dataMutableState.write { mutableState in
if mutableState.data == nil {
mutableState.data = data
} else {
mutableState.data?.append(data)
}
}
updateDownloadProgress()
}
func didReceiveResponse(_ response: HTTPURLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
dataMutableState.read { dataMutableState in
guard let httpResponseHandler = dataMutableState.httpResponseHandler else {
underlyingQueue.async { completionHandler(.allow) }
return
}
httpResponseHandler.queue.async {
httpResponseHandler.handler(response) { disposition in
if disposition == .cancel {
self.mutableState.write { mutableState in
mutableState.state = .cancelled
mutableState.error = mutableState.error ?? AFError.explicitlyCancelled
}
}
self.underlyingQueue.async {
completionHandler(disposition.sessionDisposition)
}
}
}
}
}
override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask {
let copiedRequest = request
return session.dataTask(with: copiedRequest)
@@ -1179,7 +1225,48 @@ public class DataRequest: Request {
withResult: result)
}
$validators.write { $0.append(validator) }
validators.write { $0.append(validator) }
return self
}
/// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse` and providing a completion
/// handler to return a `ResponseDisposition` value.
///
/// - Parameters:
/// - queue: `DispatchQueue` on which the closure will be called. `.main` by default.
/// - handler: Closure called when the instance produces an `HTTPURLResponse`. The `completionHandler` provided
/// MUST be called, otherwise the request will never complete.
///
/// - Returns: The instance.
@_disfavoredOverload
@discardableResult
public func onHTTPResponse(
on queue: DispatchQueue = .main,
perform handler: @escaping (_ response: HTTPURLResponse,
_ completionHandler: @escaping (ResponseDisposition) -> Void) -> Void
) -> Self {
dataMutableState.write { mutableState in
mutableState.httpResponseHandler = (queue, handler)
}
return self
}
/// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse`.
///
/// - Parameters:
/// - queue: `DispatchQueue` on which the closure will be called. `.main` by default.
/// - handler: Closure called when the instance produces an `HTTPURLResponse`.
///
/// - Returns: The instance.
@discardableResult
public func onHTTPResponse(on queue: DispatchQueue = .main,
perform handler: @escaping (HTTPURLResponse) -> Void) -> Self {
onHTTPResponse(on: queue) { response, completionHandler in
handler(response)
completionHandler(.allow)
}
return self
}
@@ -1259,10 +1346,13 @@ public final class DataStreamRequest: Request {
var numberOfExecutingStreams = 0
/// Completion calls enqueued while streams are still executing.
var enqueuedCompletionEvents: [() -> Void] = []
/// Handler for any `HTTPURLResponse`s received.
var httpResponseHandler: (queue: DispatchQueue,
handler: (_ response: HTTPURLResponse,
_ completionHandler: @escaping (ResponseDisposition) -> Void) -> Void)?
}
@Protected
var streamMutableState = StreamMutableState()
let streamMutableState = Protected(StreamMutableState())
/// Creates a `DataStreamRequest` using the provided parameters.
///
@@ -1306,7 +1396,7 @@ public final class DataStreamRequest: Request {
}
override func finish(error: AFError? = nil) {
$streamMutableState.write { state in
streamMutableState.write { state in
state.outputStream?.close()
}
@@ -1314,8 +1404,8 @@ public final class DataStreamRequest: Request {
}
func didReceive(data: Data) {
$streamMutableState.write { state in
#if !(os(Linux) || os(Windows))
streamMutableState.write { state in
#if !canImport(FoundationNetworking) // If we not using swift-corelibs-foundation.
if let stream = state.outputStream {
underlyingQueue.async {
var bytes = Array(data)
@@ -1329,6 +1419,30 @@ public final class DataStreamRequest: Request {
}
}
func didReceiveResponse(_ response: HTTPURLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
streamMutableState.read { dataMutableState in
guard let httpResponseHandler = dataMutableState.httpResponseHandler else {
underlyingQueue.async { completionHandler(.allow) }
return
}
httpResponseHandler.queue.async {
httpResponseHandler.handler(response) { disposition in
if disposition == .cancel {
self.mutableState.write { mutableState in
mutableState.state = .cancelled
mutableState.error = mutableState.error ?? AFError.explicitlyCancelled
}
}
self.underlyingQueue.async {
completionHandler(disposition.sessionDisposition)
}
}
}
}
}
/// Validates the `URLRequest` and `HTTPURLResponse` received for the instance using the provided `Validation` closure.
///
/// - Parameter validation: `Validation` closure used to validate the request and response.
@@ -1351,12 +1465,12 @@ public final class DataStreamRequest: Request {
withResult: result)
}
$validators.write { $0.append(validator) }
validators.write { $0.append(validator) }
return self
}
#if !(os(Linux) || os(Windows))
#if !canImport(FoundationNetworking) // If we not using swift-corelibs-foundation.
/// Produces an `InputStream` that receives the `Data` received by the instance.
///
/// - Note: The `InputStream` produced by this method must have `open()` called before being able to read `Data`.
@@ -1370,7 +1484,7 @@ public final class DataStreamRequest: Request {
defer { resume() }
var inputStream: InputStream?
$streamMutableState.write { state in
streamMutableState.write { state in
Foundation.Stream.getBoundStreams(withBufferSize: bufferSize,
inputStream: &inputStream,
outputStream: &state.outputStream)
@@ -1381,6 +1495,47 @@ public final class DataStreamRequest: Request {
}
#endif
/// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse` and providing a completion
/// handler to return a `ResponseDisposition` value.
///
/// - Parameters:
/// - queue: `DispatchQueue` on which the closure will be called. `.main` by default.
/// - handler: Closure called when the instance produces an `HTTPURLResponse`. The `completionHandler` provided
/// MUST be called, otherwise the request will never complete.
///
/// - Returns: The instance.
@_disfavoredOverload
@discardableResult
public func onHTTPResponse(
on queue: DispatchQueue = .main,
perform handler: @escaping (_ response: HTTPURLResponse,
_ completionHandler: @escaping (ResponseDisposition) -> Void) -> Void
) -> Self {
streamMutableState.write { mutableState in
mutableState.httpResponseHandler = (queue, handler)
}
return self
}
/// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse`.
///
/// - Parameters:
/// - queue: `DispatchQueue` on which the closure will be called. `.main` by default.
/// - handler: Closure called when the instance produces an `HTTPURLResponse`.
///
/// - Returns: The instance.
@discardableResult
public func onHTTPResponse(on queue: DispatchQueue = .main,
perform handler: @escaping (HTTPURLResponse) -> Void) -> Self {
onHTTPResponse(on: queue) { response, completionHandler in
handler(response)
completionHandler(.allow)
}
return self
}
func capturingError(from closure: () throws -> Void) {
do {
try closure()
@@ -1395,7 +1550,7 @@ public final class DataStreamRequest: Request {
appendResponseSerializer {
self.underlyingQueue.async {
self.responseSerializerDidComplete {
self.$streamMutableState.write { state in
self.streamMutableState.write { state in
guard state.numberOfExecutingStreams == 0 else {
state.enqueuedCompletionEvents.append {
self.enqueueCompletion(on: queue, stream: stream)
@@ -1546,23 +1701,22 @@ public class DownloadRequest: Request {
}
/// Protected mutable state specific to `DownloadRequest`.
@Protected
private var mutableDownloadState = DownloadRequestMutableState()
private let mutableDownloadState = Protected(DownloadRequestMutableState())
/// If the download is resumable and is eventually cancelled or fails, this value may be used to resume the download
/// using the `download(resumingWith data:)` API.
///
/// - Note: For more information about `resumeData`, see [Apple's documentation](https://developer.apple.com/documentation/foundation/urlsessiondownloadtask/1411634-cancel).
public var resumeData: Data? {
#if !(os(Linux) || os(Windows))
return $mutableDownloadState.resumeData ?? error?.downloadResumeData
#if !canImport(FoundationNetworking) // If we not using swift-corelibs-foundation.
return mutableDownloadState.resumeData ?? error?.downloadResumeData
#else
return $mutableDownloadState.resumeData
return mutableDownloadState.resumeData
#endif
}
/// If the download is successful, the `URL` where the file was downloaded.
public var fileURL: URL? { $mutableDownloadState.fileURL }
public var fileURL: URL? { mutableDownloadState.fileURL }
// MARK: Initial State
@@ -1605,7 +1759,7 @@ public class DownloadRequest: Request {
override func reset() {
super.reset()
$mutableDownloadState.write {
mutableDownloadState.write {
$0.resumeData = nil
$0.fileURL = nil
}
@@ -1620,7 +1774,7 @@ public class DownloadRequest: Request {
eventMonitor?.request(self, didFinishDownloadingUsing: task, with: result)
switch result {
case let .success(url): $mutableDownloadState.fileURL = url
case let .success(url): mutableDownloadState.fileURL = url
case let .failure(error): self.error = error
}
}
@@ -1698,7 +1852,7 @@ public class DownloadRequest: Request {
///
/// - Returns: The instance.
private func cancel(optionallyProducingResumeData completionHandler: ((_ resumeData: Data?) -> Void)?) -> Self {
$mutableState.write { mutableState in
mutableState.write { mutableState in
guard mutableState.state.canTransitionTo(.cancelled) else { return }
mutableState.state = .cancelled
@@ -1714,7 +1868,7 @@ public class DownloadRequest: Request {
// Resume to ensure metrics are gathered.
task.resume()
task.cancel { resumeData in
self.$mutableDownloadState.resumeData = resumeData
self.mutableDownloadState.resumeData = resumeData
self.underlyingQueue.async { self.didCancelTask(task) }
completionHandler(resumeData)
}
@@ -1754,7 +1908,7 @@ public class DownloadRequest: Request {
withResult: result)
}
$validators.write { $0.append(validator) }
validators.write { $0.append(validator) }
return self
}

View File

@@ -131,7 +131,7 @@ struct RequestTaskMap {
switch (events.completed, events.metricsGathered) {
case (true, _): fatalError("RequestTaskMap consistency error: duplicate completionReceivedForTask call.")
#if os(Linux) // Linux doesn't gather metrics, so unconditionally remove the reference and return true.
#if os(Linux) || os(Android) // Linux doesn't gather metrics, so unconditionally remove the reference and return true.
default: self[task] = nil; return true
#else
case (false, false):

View File

@@ -1153,7 +1153,7 @@ extension DataStreamRequest {
}
}
$streamMutableState.write { $0.streams.append(parser) }
streamMutableState.write { $0.streams.append(parser) }
appendStreamCompletion(on: queue, stream: stream)
return self
@@ -1195,7 +1195,7 @@ extension DataStreamRequest {
}
}
$streamMutableState.write { $0.streams.append(parser) }
streamMutableState.write { $0.streams.append(parser) }
appendStreamCompletion(on: queue, stream: stream)
return self
@@ -1230,14 +1230,14 @@ extension DataStreamRequest {
}
}
$streamMutableState.write { $0.streams.append(parser) }
streamMutableState.write { $0.streams.append(parser) }
appendStreamCompletion(on: queue, stream: stream)
return self
}
private func updateAndCompleteIfPossible() {
$streamMutableState.write { state in
streamMutableState.write { state in
state.numberOfExecutingStreams -= 1
guard state.numberOfExecutingStreams == 0, !state.enqueuedCompletionEvents.isEmpty else { return }

View File

@@ -1,5 +1,5 @@
//
// ServerTrustPolicy.swift
// ServerTrustEvaluation.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
@@ -48,7 +48,7 @@ open class ServerTrustManager {
self.evaluators = evaluators
}
#if !(os(Linux) || os(Windows))
#if canImport(Security)
/// Returns the `ServerTrustEvaluating` value for the given host, if one is set.
///
/// By default, this method will return the policy that perfectly matches the given host. Subclasses could override
@@ -75,8 +75,8 @@ open class ServerTrustManager {
/// A protocol describing the API used to evaluate server trusts.
public protocol ServerTrustEvaluating {
#if os(Linux) || os(Windows)
// Implement this once Linux/Windows has API for evaluating server trusts.
#if !canImport(Security)
// Implement this once other platforms have API for evaluating server trusts.
#else
/// Evaluates the given `SecTrust` value for the given `host`.
///
@@ -91,7 +91,7 @@ public protocol ServerTrustEvaluating {
// MARK: - Server Trust Evaluators
#if !(os(Linux) || os(Windows))
#if canImport(Security)
/// An evaluator which uses the default server trust evaluation while allowing you to control whether to validate the
/// host provided by the challenge. Applications are encouraged to always validate the host in production environments
/// to guarantee the validity of the server's certificate chain.
@@ -181,6 +181,15 @@ public final class RevocationTrustEvaluator: ServerTrustEvaluating {
try trust.af.performValidation(forHost: host)
}
#if swift(>=5.9)
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, visionOS 1, *) {
try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options))
} else {
try trust.af.validate(policy: SecPolicy.af.revocation(options: options)) { status, result in
AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options))
}
}
#else
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options))
} else {
@@ -188,6 +197,7 @@ public final class RevocationTrustEvaluator: ServerTrustEvaluating {
AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options))
}
}
#endif
}
}
@@ -355,10 +365,8 @@ public final class PublicKeysTrustEvaluator: ServerTrustEvaluating {
let pinnedKeysInServerKeys: Bool = {
for serverPublicKey in trust.af.publicKeys {
for pinnedPublicKey in keys {
if serverPublicKey == pinnedPublicKey {
return true
}
if keys.contains(serverPublicKey) {
return true
}
}
return false
@@ -449,7 +457,7 @@ public final class DisabledTrustEvaluator: ServerTrustEvaluating {
// MARK: - Extensions
extension Array where Element == ServerTrustEvaluating {
#if os(Linux) || os(Windows)
#if os(Linux) || os(Windows) || os(Android)
// Add this same convenience method for Linux/Windows.
#else
/// Evaluates the given `SecTrust` value for the given `host`.
@@ -598,7 +606,15 @@ extension AlamofireExtension where ExtendedType == SecTrust {
/// The `SecCertificate`s contained in `self`.
public var certificates: [SecCertificate] {
#if swift(>=5.5.1) // Xcode 13.1 / 2021 SDKs.
#if swift(>=5.9)
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, visionOS 1, *) {
return (SecTrustCopyCertificateChain(type) as? [SecCertificate]) ?? []
} else {
return (0..<SecTrustGetCertificateCount(type)).compactMap { index in
SecTrustGetCertificateAtIndex(type, index)
}
}
#elseif swift(>=5.5.1) // Xcode 13.1 / 2021 SDKs.
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
return (SecTrustCopyCertificateChain(type) as? [SecCertificate]) ?? []
} else {
@@ -623,6 +639,15 @@ extension AlamofireExtension where ExtendedType == SecTrust {
/// - Parameter host: The hostname, used only in the error output if validation fails.
/// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason.
public func performDefaultValidation(forHost host: String) throws {
#if swift(>=5.9)
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, visionOS 1, *) {
try evaluate(afterApplying: SecPolicy.af.default)
} else {
try validate(policy: SecPolicy.af.default) { status, result in
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, type, status, result)))
}
}
#else
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
try evaluate(afterApplying: SecPolicy.af.default)
} else {
@@ -630,6 +655,7 @@ extension AlamofireExtension where ExtendedType == SecTrust {
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, type, status, result)))
}
}
#endif
}
/// Validates `self` after applying `SecPolicy.af.hostname(host)`, which performs the default validation as well as
@@ -638,6 +664,15 @@ extension AlamofireExtension where ExtendedType == SecTrust {
/// - Parameter host: The hostname to use in the validation.
/// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason.
public func performValidation(forHost host: String) throws {
#if swift(>=5.9)
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, visionOS 1, *) {
try evaluate(afterApplying: SecPolicy.af.hostname(host))
} else {
try validate(policy: SecPolicy.af.hostname(host)) { status, result in
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, type, status, result)))
}
}
#else
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
try evaluate(afterApplying: SecPolicy.af.hostname(host))
} else {
@@ -645,6 +680,7 @@ extension AlamofireExtension where ExtendedType == SecTrust {
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, type, status, result)))
}
}
#endif
}
}
@@ -704,11 +740,19 @@ extension AlamofireExtension where ExtendedType == SecCertificate {
guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil }
#if swift(>=5.9)
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, visionOS 1, *) {
return SecTrustCopyKey(createdTrust)
} else {
return SecTrustCopyPublicKey(createdTrust)
}
#else
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) {
return SecTrustCopyKey(createdTrust)
} else {
return SecTrustCopyPublicKey(createdTrust)
}
#endif
}
}

View File

@@ -94,7 +94,7 @@ extension SessionDelegate: URLSessionTaskDelegate {
case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM,
NSURLAuthenticationMethodNegotiate:
evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task)
#if !(os(Linux) || os(Windows))
#if canImport(Security)
case NSURLAuthenticationMethodServerTrust:
evaluation = attemptServerTrustAuthentication(with: challenge)
case NSURLAuthenticationMethodClientCertificate:
@@ -111,7 +111,7 @@ extension SessionDelegate: URLSessionTaskDelegate {
completionHandler(evaluation.disposition, evaluation.credential)
}
#if !(os(Linux) || os(Windows))
#if canImport(Security)
/// Evaluates the server trust `URLAuthenticationChallenge` received.
///
/// - Parameter challenge: The `URLAuthenticationChallenge`.
@@ -230,6 +230,25 @@ extension SessionDelegate: URLSessionTaskDelegate {
// MARK: URLSessionDataDelegate
extension SessionDelegate: URLSessionDataDelegate {
open func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: response)
guard let response = response as? HTTPURLResponse else { completionHandler(.allow); return }
if let request = request(for: dataTask, as: DataRequest.self) {
request.didReceiveResponse(response, completionHandler: completionHandler)
} else if let request = request(for: dataTask, as: DataStreamRequest.self) {
request.didReceiveResponse(response, completionHandler: completionHandler)
} else {
assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive response")
completionHandler(.allow)
return
}
}
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data)
@@ -238,7 +257,7 @@ extension SessionDelegate: URLSessionDataDelegate {
} else if let request = request(for: dataTask, as: DataStreamRequest.self) {
request.didReceive(data: data)
} else {
assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive")
assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive data")
return
}
}

View File

@@ -695,6 +695,74 @@ extension _URLEncodedFormEncoder.KeyedContainer: KeyedEncodingContainerProtocol
try encode(nilValue, forKey: key)
}
func encodeIfPresent(_ value: Bool?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: String?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Double?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Float?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int8?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int16?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int32?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: Int64?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt8?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws {
try _encodeIfPresent(value, forKey: key)
}
func encodeIfPresent<Value>(_ value: Value?, forKey key: Key) throws where Value: Encodable {
try _encodeIfPresent(value, forKey: key)
}
func _encodeIfPresent<Value>(_ value: Value?, forKey key: Key) throws where Value: Encodable {
if let value = value {
try encode(value, forKey: key)
} else {
try encodeNil(forKey: key)
}
}
func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
var container = nestedSingleValueEncoder(for: key)
try container.encode(value)