diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index e835eeda7..678f5cc5a 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -385,14 +385,48 @@ extension HTTPClientResponseDelegate { public func didReceiveError(task: HTTPClient.Task, _: Error) {} } -internal extension URL { +extension URL { + var percentEncodedPath: String { + if self.path.isEmpty { + return "/" + } + return self.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self.path + } + + var pathHasTrailingSlash: Bool { + if #available(OSX 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *) { + return self.hasDirectoryPath + } else { + // Most platforms should use `self.hasDirectoryPath`, but on older darwin platforms + // we have this approximation instead. + let url = self.absoluteString + + var pathEndIndex = url.index(before: url.endIndex) + if let queryIndex = url.firstIndex(of: "?") { + pathEndIndex = url.index(before: queryIndex) + } else if let fragmentIndex = url.suffix(from: url.firstIndex(of: "@") ?? url.startIndex).lastIndex(of: "#") { + pathEndIndex = url.index(before: fragmentIndex) + } + + return url[pathEndIndex] == "/" + } + } + var uri: String { - let urlEncodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? path - return path.isEmpty ? "/" : urlEncodedPath + (query.map { "?" + $0 } ?? "") + var uri = self.percentEncodedPath + if self.pathHasTrailingSlash, uri != "/" { + uri += "/" + } + + if let query = self.query { + uri += "?" + query + } + + return uri } func hasTheSameOrigin(as other: URL) -> Bool { - return host == other.host && scheme == other.scheme && port == other.port + return self.host == other.host && self.scheme == other.scheme && self.port == other.port } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift index 6ad239e02..ad172d12b 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift @@ -30,6 +30,7 @@ extension HTTPClientInternalTests { ("testProxyStreaming", testProxyStreaming), ("testProxyStreamingFailure", testProxyStreamingFailure), ("testUploadStreamingBackpressure", testUploadStreamingBackpressure), + ("testRequestURITrailingSlash", testRequestURITrailingSlash), ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index 6d73caf95..e17d0e871 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -193,4 +193,39 @@ class HTTPClientInternalTests: XCTestCase { XCTAssertEqual(delegate.reads, 3) } + + func testRequestURITrailingSlash() throws { + let request1 = try Request(url: "https://someserver.com:8888/some/path?foo=bar#ref") + XCTAssertEqual(request1.url.uri, "/some/path?foo=bar") + + let request2 = try Request(url: "https://someserver.com:8888/some/path/?foo=bar#ref") + XCTAssertEqual(request2.url.uri, "/some/path/?foo=bar") + + let request3 = try Request(url: "https://someserver.com:8888?foo=bar#ref") + XCTAssertEqual(request3.url.uri, "/?foo=bar") + + let request4 = try Request(url: "https://someserver.com:8888/?foo=bar#ref") + XCTAssertEqual(request4.url.uri, "/?foo=bar") + + let request5 = try Request(url: "https://someserver.com:8888/some/path") + XCTAssertEqual(request5.url.uri, "/some/path") + + let request6 = try Request(url: "https://someserver.com:8888/some/path/") + XCTAssertEqual(request6.url.uri, "/some/path/") + + let request7 = try Request(url: "https://someserver.com:8888") + XCTAssertEqual(request7.url.uri, "/") + + let request8 = try Request(url: "https://someserver.com:8888/") + XCTAssertEqual(request8.url.uri, "/") + + let request9 = try Request(url: "https://someserver.com:8888#ref") + XCTAssertEqual(request9.url.uri, "/") + + let request10 = try Request(url: "https://someserver.com:8888/#ref") + XCTAssertEqual(request10.url.uri, "/") + + let request11 = try Request(url: "https://someserver.com/some%20path") + XCTAssertEqual(request11.url.uri, "/some%20path") + } }