From 6b6145883567c94c1accb19486b83007058bd84d Mon Sep 17 00:00:00 2001 From: Rick Newton-Rogers Date: Wed, 2 Oct 2024 17:26:01 +0100 Subject: [PATCH] workaround Foundation.URL behavior changes `Foundation.URL` has various behavior changes in Swift 6 to better match RFC 3986 which impact AHC. In particular it now no longer strips the square brackets in IPv6 hosts which are not tolerated by `inet_pton` so these must be manually stripped. --- .../HTTPClientRequest+Prepared.swift | 2 +- .../AsyncHTTPClient/DeconstructedURL.swift | 31 +++++++++++++++++++ .../HTTPClientTests.swift | 6 +++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift index 2d5e3e2e0..0a3ec6442 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift @@ -45,7 +45,7 @@ extension HTTPClientRequest { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientRequest.Prepared { init(_ request: HTTPClientRequest, dnsOverride: [String: String] = [:]) throws { - guard let url = URL(string: request.url) else { + guard !request.url.isEmpty, let url = URL(string: request.url) else { throw HTTPClientError.invalidURL } diff --git a/Sources/AsyncHTTPClient/DeconstructedURL.swift b/Sources/AsyncHTTPClient/DeconstructedURL.swift index 020c17455..4de875a4a 100644 --- a/Sources/AsyncHTTPClient/DeconstructedURL.swift +++ b/Sources/AsyncHTTPClient/DeconstructedURL.swift @@ -48,9 +48,16 @@ extension DeconstructedURL { switch scheme { case .http, .https: + #if os(Linux) && compiler(>=6.0) + guard var urlHost = url.host, !urlHost.isEmpty else { + throw HTTPClientError.emptyHost + } + let host = urlHost.trimIPv6Brackets() + #else guard let host = url.host, !host.isEmpty else { throw HTTPClientError.emptyHost } + #endif self.init( scheme: scheme, connectionTarget: .init(remoteHost: host, port: url.port ?? scheme.defaultPort), @@ -81,3 +88,27 @@ extension DeconstructedURL { } } } + +#if os(Linux) && compiler(>=6.0) +extension String { + @inlinable internal func trimIPv6Brackets() -> String { + var substring = self[...] + let utf8View = self.utf8 + + var modified = false + if utf8View.first == "[".utf8.first { + substring = substring.dropFirst() + modified = true + } + if utf8View.last == "]".utf8.last { + substring = substring.dropLast() + modified = true + } + + if modified { + return String(substring) + } + return self + } +} +#endif diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 0c259c1bd..9b0ad587e 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -43,8 +43,12 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertEqual(request2.url.path, "") let request3 = try Request(url: "unix:///tmp/file") - XCTAssertNil(request3.url.host) XCTAssertEqual(request3.host, "") + #if os(Linux) && compiler(>=6.0) + XCTAssertEqual(request3.url.host, "") + #else + XCTAssertNil(request3.url.host) + #endif XCTAssertEqual(request3.url.path, "/tmp/file") XCTAssertEqual(request3.port, 80) XCTAssertFalse(request3.useTLS)