Skip to content

Commit 2ee7c89

Browse files
committed
(143159003) Don't encode colon if URLComponents path starts with colon (swiftlang#1139)
1 parent b215b9a commit 2ee7c89

File tree

2 files changed

+23
-4
lines changed

2 files changed

+23
-4
lines changed

Sources/FoundationEssentials/URL/URLComponents.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -367,14 +367,23 @@ public struct URLComponents: Hashable, Equatable, Sendable {
367367
}
368368

369369
private var percentEncodedPathNoColon: String {
370-
guard percentEncodedPath.utf8.first(where: { $0 == ._colon || $0 == ._slash }) == ._colon else {
371-
return percentEncodedPath
370+
let p = percentEncodedPath
371+
guard p.utf8.first(where: { $0 == ._colon || $0 == ._slash }) == ._colon else {
372+
return p
372373
}
373-
let colonEncodedPath = Array(percentEncodedPath.utf8).replacing(
374+
if p.utf8.first == ._colon {
375+
// In the rare case that an app relies on URL allowing an empty
376+
// scheme and passes its URL string directly to URLComponents
377+
// to modify other components, we need to return the path without
378+
// encoding the colons.
379+
return p
380+
}
381+
let firstSlash = p.utf8.firstIndex(of: ._slash) ?? p.endIndex
382+
let colonEncodedSegment = Array(p[..<firstSlash].utf8).replacing(
374383
[._colon],
375384
with: [UInt8(ascii: "%"), UInt8(ascii: "3"), UInt8(ascii: "A")]
376385
)
377-
return String(decoding: colonEncodedPath, as: UTF8.self)
386+
return String(decoding: colonEncodedSegment, as: UTF8.self) + p[firstSlash...]
378387
}
379388

380389
mutating func setPercentEncodedPath(_ newValue: String) throws {

Tests/FoundationEssentialsTests/URLTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,16 @@ final class URLTests : XCTestCase {
13751375

13761376
XCTAssertNotNil(URL(string: comp.string!))
13771377
XCTAssertNotNil(URLComponents(string: comp.string!))
1378+
1379+
// In rare cases, an app might rely on URL allowing an empty scheme,
1380+
// but then take that string and pass it to URLComponents to modify
1381+
// other components of the URL. We shouldn't percent-encode the colon
1382+
// in these cases.
1383+
1384+
let url = try XCTUnwrap(URL(string: "://host/path"))
1385+
comp = try XCTUnwrap(URLComponents(string: url.absoluteString))
1386+
comp.query = "key=value"
1387+
XCTAssertEqual(comp.string, "://host/path?key=value")
13781388
}
13791389

13801390
func testURLComponentsInvalidPaths() {

0 commit comments

Comments
 (0)