From 8aa3ad5e4bb74ec326ef7be787366c48b07d3402 Mon Sep 17 00:00:00 2001 From: Hamzah Malik Date: Wed, 23 Sep 2026 08:19:35 +0100 Subject: [PATCH] Preserve per-request TLSConfiguration across redirects --- .../HTTPClientRequest+Prepared.swift | 1 + Sources/AsyncHTTPClient/HTTPHandler.swift | 3 ++- .../AsyncAwaitEndToEndTests.swift | 26 +++++++++++++++++++ .../HTTPClientTests.swift | 26 +++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift index 3e4482292..70d158460 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift @@ -162,6 +162,7 @@ extension HTTPClientRequest { newRequest.headers = headers newRequest.body = body newRequest.localAddress = self.localAddress + newRequest.tlsConfiguration = self.tlsConfiguration return newRequest } } diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 432563d15..b6fdbfeac 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -1090,7 +1090,8 @@ internal struct RedirectHandler { url: redirectURL, method: method, headers: headers, - body: body + body: body, + tlsConfiguration: self.request.tlsConfiguration ) let newTask = self.execute(newRequest, redirectState) diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index 78dec4296..12d1c0e14 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -1109,6 +1109,32 @@ final class AsyncAwaitEndToEndTests: XCTestCase { } } + func testRedirectPreservesPerRequestTLSConfiguration() { + XCTAsyncTest { + let bin = HTTPBin(.http2(compress: false)) + defer { XCTAssertNoThrow(try bin.shutdown()) } + // No client-level TLS configuration: default verification rejects HTTPBin's self-signed certificate, + // so the redirected request only succeeds if the per-request configuration is carried over. + let client = HTTPClient(eventLoopGroupProvider: .singleton) + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) + + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/redirect/302") + var tlsConfiguration = TLSConfiguration.makeClientConfiguration() + tlsConfiguration.certificateVerification = .none + request.tlsConfiguration = tlsConfiguration + + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } + + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(response.history.count, 2) + } + } + // MARK: - Integration tests: local address binding func testLocalAddressBinding_configLevel() async throws { diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 75371d437..83bbaf3ae 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -697,6 +697,32 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { ) } + func testHttpRedirectPreservesPerRequestTLSConfiguration() throws { + let httpsBin = HTTPBin(.http1_1(ssl: true)) + // No client-level TLS configuration: default verification rejects HTTPBin's self-signed certificate, + // so the redirected request only succeeds if the per-request configuration is carried over. + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(redirectConfiguration: .follow(max: 10, allowCycles: true)) + ) + + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try httpsBin.shutdown()) + } + + var tlsConfiguration = TLSConfiguration.makeClientConfiguration() + tlsConfiguration.certificateVerification = .none + let request = try HTTPClient.Request( + url: "https://localhost:\(httpsBin.port)/redirect/302", + tlsConfiguration: tlsConfiguration + ) + + let response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(response.history.count, 2) + } + func testHttpHostRedirect() { let localClient = HTTPClient( eventLoopGroupProvider: .shared(self.clientGroup),