Skip to content

Commit 0e6c827

Browse files
authored
(143159003) Don't encode colon if URLComponents path starts with colon (#1139)
1 parent a24abf5 commit 0e6c827

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
@@ -1355,6 +1355,16 @@ final class URLTests : XCTestCase {
13551355

13561356
XCTAssertNotNil(URL(string: comp.string!))
13571357
XCTAssertNotNil(URLComponents(string: comp.string!))
1358+
1359+
// In rare cases, an app might rely on URL allowing an empty scheme,
1360+
// but then take that string and pass it to URLComponents to modify
1361+
// other components of the URL. We shouldn't percent-encode the colon
1362+
// in these cases.
1363+
1364+
let url = try XCTUnwrap(URL(string: "://host/path"))
1365+
comp = try XCTUnwrap(URLComponents(string: url.absoluteString))
1366+
comp.query = "key=value"
1367+
XCTAssertEqual(comp.string, "://host/path?key=value")
13581368
}
13591369

13601370
func testURLComponentsInvalidPaths() {

0 commit comments

Comments
 (0)