Skip to content

Commit 26f4864

Browse files
authored
(138168197) Restore URL.host bracket stripping for compatibility (#1008) (#1023)
1 parent a28f6ac commit 26f4864

File tree

2 files changed

+65
-12
lines changed

2 files changed

+65
-12
lines changed

Sources/FoundationEssentials/URL/URL.swift

+29-12
Original file line numberDiff line numberDiff line change
@@ -1207,21 +1207,38 @@ public struct URL: Equatable, Sendable, Hashable {
12071207
return nil
12081208
}
12091209
#endif
1210-
guard let encodedHost else { return nil }
1211-
let didPercentEncodeHost = hasAuthority ? _parseInfo.didPercentEncodeHost : _baseParseInfo?.didPercentEncodeHost ?? false
1212-
if percentEncoded {
1213-
if didPercentEncodeHost {
1214-
return String(encodedHost)
1215-
}
1216-
guard let decoded = Parser.IDNADecodeHost(encodedHost) else {
1210+
guard let encodedHost else {
1211+
return nil
1212+
}
1213+
1214+
func requestedHost() -> String? {
1215+
let didPercentEncodeHost = hasAuthority ? _parseInfo.didPercentEncodeHost : _baseParseInfo?.didPercentEncodeHost ?? false
1216+
if percentEncoded {
1217+
if didPercentEncodeHost {
1218+
return encodedHost
1219+
}
1220+
guard let decoded = Parser.IDNADecodeHost(encodedHost) else {
1221+
return encodedHost
1222+
}
1223+
return Parser.percentEncode(decoded, component: .host)
1224+
} else {
1225+
if didPercentEncodeHost {
1226+
return Parser.percentDecode(encodedHost)
1227+
}
12171228
return encodedHost
12181229
}
1219-
return Parser.percentEncode(decoded, component: .host)
1230+
}
1231+
1232+
guard let requestedHost = requestedHost() else {
1233+
return nil
1234+
}
1235+
1236+
let isIPLiteral = hasAuthority ? _parseInfo.isIPLiteral : _baseParseInfo?.isIPLiteral ?? false
1237+
if isIPLiteral {
1238+
// Strip square brackets to be compatible with old URL.host behavior
1239+
return String(requestedHost.utf8.dropFirst().dropLast())
12201240
} else {
1221-
if didPercentEncodeHost {
1222-
return Parser.percentDecode(encodedHost)
1223-
}
1224-
return String(encodedHost)
1241+
return requestedHost
12251242
}
12261243
}
12271244

Tests/FoundationEssentialsTests/URLTests.swift

+36
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,42 @@ final class URLTests : XCTestCase {
792792
XCTAssertEqual(url.host, "*.xn--poema-9qae5a.com.br")
793793
}
794794

795+
func testURLHostIPLiteralCompatibility() throws {
796+
var url = URL(string: "http://[::]")!
797+
XCTAssertEqual(url.host, "::")
798+
XCTAssertEqual(url.host(), "::")
799+
800+
url = URL(string: "https://[::1]:433/")!
801+
XCTAssertEqual(url.host, "::1")
802+
XCTAssertEqual(url.host(), "::1")
803+
804+
url = URL(string: "https://[2001:db8::]/")!
805+
XCTAssertEqual(url.host, "2001:db8::")
806+
XCTAssertEqual(url.host(), "2001:db8::")
807+
808+
url = URL(string: "https://[2001:db8::]:433")!
809+
XCTAssertEqual(url.host, "2001:db8::")
810+
XCTAssertEqual(url.host(), "2001:db8::")
811+
812+
url = URL(string: "http://[fe80::a%25en1]")!
813+
XCTAssertEqual(url.absoluteString, "http://[fe80::a%25en1]")
814+
XCTAssertEqual(url.host, "fe80::a%en1")
815+
XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25en1")
816+
XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%en1")
817+
818+
url = URL(string: "http://[fe80::a%en1]")!
819+
XCTAssertEqual(url.absoluteString, "http://[fe80::a%25en1]")
820+
XCTAssertEqual(url.host, "fe80::a%en1")
821+
XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25en1")
822+
XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%en1")
823+
824+
url = URL(string: "http://[fe80::a%100%CustomZone]")!
825+
XCTAssertEqual(url.absoluteString, "http://[fe80::a%25100%25CustomZone]")
826+
XCTAssertEqual(url.host, "fe80::a%100%CustomZone")
827+
XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25100%25CustomZone")
828+
XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%100%CustomZone")
829+
}
830+
795831
func testURLTildeFilePath() throws {
796832
var url = URL(filePath: "~")
797833
// "~" must either be expanded to an absolute path or resolved against a base URL

0 commit comments

Comments
 (0)