Skip to content

preserve trailing slash in uri path #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 20, 2019
42 changes: 38 additions & 4 deletions Sources/AsyncHTTPClient/HTTPHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -385,14 +385,48 @@ extension HTTPClientResponseDelegate {
public func didReceiveError(task: HTTPClient.Task<Response>, _: 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)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the else branch? The minimum versions above were all released in 2015, making them 4 years old this year. Can we get away without it?

If the answer is really no, then can we also add a comment to this code branch to say that the code is not 100% correct, but is considered "good enough"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by getting away without it? Adding supported platforms in Package.swift or a precondition? I would prefer not to have an alternative clause here as well...
@tomerd @tanner0101 @ianpartridge do you think we should limit the client to modern(er) platforms?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given our main use case is linux, restricting older non-linux platforms in Package.swift seems fine to me


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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extension HTTPClientInternalTests {
("testProxyStreaming", testProxyStreaming),
("testProxyStreamingFailure", testProxyStreamingFailure),
("testUploadStreamingBackpressure", testUploadStreamingBackpressure),
("testRequestURITrailingSlash", testRequestURITrailingSlash),
]
}
}
35 changes: 35 additions & 0 deletions Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}