Skip to content

Commit ea8a853

Browse files
authored
Merge branch 'main' into SecurityRefactoringWithGuards
2 parents 56d04c5 + 688ed3c commit ea8a853

2 files changed

Lines changed: 51 additions & 55 deletions

File tree

Sources/SwiftRestRequests/RestApiCaller.swift

Lines changed: 50 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,11 @@ open class RestApiCaller : NSObject {
7272
let errorDeserializer: (any Deserializer)?
7373
/// Cookie storage used by the session (if provided).
7474
let httpCookieStorage: HTTPCookieStorage?
75+
76+
private let interceptorLock = NSLock()
7577

76-
/// Registered request/response interceptors.
77-
var interceptors: [any URLRequestInterceptor]?
78+
/// Registered request/response interceptors (empty by default).
79+
var interceptors: [URLRequestInterceptor] = []
7880

7981
/// Closure used to generate dynamic headers prior to each request.
8082
public let headerGenerator: HeaderGenerator?
@@ -150,19 +152,15 @@ open class RestApiCaller : NSObject {
150152

151153
@inline(__always)
152154
private func callInvokeInterceptors(_ request: inout URLRequest) {
153-
if let interceptors {
154-
for interceptor in interceptors {
155-
interceptor.invokeRequest(request: &request, for: session)
156-
}
155+
for interceptor in interceptors {
156+
interceptor.invokeRequest(request: &request, for: session)
157157
}
158158
}
159159
@inline(__always)
160160
private func callReceiveInterceptors(_ data: Data, _ response: HTTPURLResponse) {
161-
if let interceptors {
162-
// we revers interceptor chain when receiving...
163-
for interceptor in interceptors.reversed() {
164-
interceptor.receiveResponse(data: data, response: response, for: session)
165-
}
161+
// reverse the order for response handling so the most recently added interceptor observes the response first
162+
for interceptor in interceptors.reversed() {
163+
interceptor.receiveResponse(data: data, response: response, for: session)
166164
}
167165
}
168166

@@ -279,10 +277,9 @@ open class RestApiCaller : NSObject {
279277
let httpStatus = httpResponse.status
280278

281279
// For requests without deserialization and no error just return the status
282-
if type(of: responseDeserializer) == VoidDeserializer.self || httpStatus == .noContent {
283-
if httpStatus.type == .success {
284-
return (nil, httpResponse.status)
285-
}
280+
if shouldBypassDeserialization(responseDeserializer, status: httpStatus),
281+
httpStatus.type == .success {
282+
return (nil, httpStatus)
286283
}
287284

288285
guard !data.isEmpty else {
@@ -291,52 +288,50 @@ open class RestApiCaller : NSObject {
291288

292289
// Postcondition: We have a response object or error that needs to be parsed!
293290

294-
let contentType = httpResponse.value(forHTTPHeaderField: HTTPHeaderKeys.ContentType.rawValue)
291+
_ = try validatedMimeType(from: httpResponse)
295292

296-
// Note: some servers return also encoding i.e. Content-Type: application/json; charset=utf-8 take the first part
293+
if httpStatus.type == .success {
294+
let transformedResponse = try decodeSuccessfulResponse(data: data, response: httpResponse, deserializer: responseDeserializer)
295+
return (transformedResponse, httpStatus)
296+
}
297+
298+
throw try buildErrorResponse(data: data, response: httpResponse, status: httpStatus)
299+
}
300+
301+
private func shouldBypassDeserialization<T: Deserializer>(_ deserializer: T, status: HTTPStatusCode) -> Bool {
302+
(deserializer is VoidDeserializer) || status == .noContent
303+
}
304+
305+
private func validatedMimeType(from response: HTTPURLResponse) throws -> MimeType {
306+
let contentType = response.value(forHTTPHeaderField: HTTPHeaderKeys.ContentType.rawValue)
297307
let firstContentMimeType = contentType?.components(separatedBy: ";").first
298308

299-
guard let firstContentMimeType, let _ = MimeType(rawValue: firstContentMimeType) else {
309+
guard let firstContentMimeType,
310+
let mimeType = MimeType(rawValue: firstContentMimeType) else {
300311
throw RestError.invalidMimeType(contentType)
301312
}
302-
303-
// Postcondition: Response or error ContentTyp is supported
304-
305-
if httpStatus.type == .success {
306-
307-
// Postcondition: httpStatus in 200...299
308-
if httpStatus == .ok {
309-
// Postcondition: httpStatus is 200 we need to deserialize
310-
do {
311-
let transformedResponse = try responseDeserializer.deserialize(data)
312-
return (transformedResponse, httpResponse.status)
313-
} catch {
314-
throw RestError.malformedResponse(httpResponse, data, error)
315-
}
316-
} else {
317-
// Postcondition: httpStatus is 201...299
318-
// Note: we skipt data in this case
319-
return (nil, httpStatus)
320-
}
321-
322-
} else {
323-
324-
// Postcondition: httpStatus not 2XX. We have an error and error data
325-
var failedRestCallError: RestError
326-
313+
return mimeType
314+
}
315+
316+
private func decodeSuccessfulResponse<T: Deserializer>(data: Data, response: HTTPURLResponse, deserializer: T) throws -> T.ResponseType? {
317+
if response.status == .ok {
327318
do {
328-
let errorJson = try errorDeserializer?.deserialize(data)
329-
failedRestCallError = RestError.failedRestCall(httpResponse, httpStatus, error: errorJson)
319+
return try deserializer.deserialize(data)
330320
} catch {
331-
throw RestError.malformedResponse(httpResponse, data, error)
321+
throw RestError.malformedResponse(response, data, error)
332322
}
333-
334-
throw failedRestCallError
335-
336323
}
337-
324+
return nil
338325
}
339326

327+
private func buildErrorResponse(data: Data, response: HTTPURLResponse, status: HTTPStatusCode) throws -> RestError {
328+
do {
329+
let errorPayload = try errorDeserializer?.deserialize(data)
330+
return RestError.failedRestCall(response, status, error: errorPayload)
331+
} catch {
332+
throw RestError.malformedResponse(response, data, error)
333+
}
334+
}
340335

341336

342337
// MARK: Public API that can be used from other classes or subclass
@@ -346,10 +341,11 @@ open class RestApiCaller : NSObject {
346341
/// - Parameter interceptor: Interceptor appended to the invocation chain.
347342
public func registerRequestInterceptor(_ interceptor: any URLRequestInterceptor) {
348343
logger.info("Registering request interceptor: \(interceptor)")
349-
if self.interceptors == nil {
350-
self.interceptors = [any URLRequestInterceptor]()
351-
}
352-
interceptors!.append(interceptor)
344+
345+
interceptorLock.lock()
346+
defer { interceptorLock.unlock() }
347+
348+
interceptors.append(interceptor)
353349
}
354350

355351
/// Executes an asynchronous `GET` request and decodes the JSON response.

Sources/SwiftRestRequests/security/URLRequestAuthorizer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class BasicRequestAuthorizer: URLRequestAuthorizer {
6565
public func configureAuthorizationHeader(for urlRequest: inout URLRequest) {
6666
logger.trace("Set HTTP Authorization header", metadata: [
6767
"urlRequest": "\(String(describing: urlRequest.url?.absoluteString))",
68-
"Authorization": "\(headerValue)"])
68+
"Authorization": "****"])
6969
urlRequest.setValue(self.headerValue, forHTTPHeaderField: "Authorization")
7070
}
7171
}

0 commit comments

Comments
 (0)