From 305bbfb194d4cd17ed2e2eac869c7c7260c236f6 Mon Sep 17 00:00:00 2001 From: Jonathan Flat <50605158+jrflat@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:14:48 -0600 Subject: [PATCH] (138168197) Restore URL.host bracket stripping for compatibility (#1008) --- Sources/FoundationEssentials/URL/URL.swift | 41 +++++++++++++------ .../FoundationEssentialsTests/URLTests.swift | 36 ++++++++++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/Sources/FoundationEssentials/URL/URL.swift b/Sources/FoundationEssentials/URL/URL.swift index 063d5f9f3..ae379bdce 100644 --- a/Sources/FoundationEssentials/URL/URL.swift +++ b/Sources/FoundationEssentials/URL/URL.swift @@ -1207,21 +1207,38 @@ public struct URL: Equatable, Sendable, Hashable { return nil } #endif - guard let encodedHost else { return nil } - let didPercentEncodeHost = hasAuthority ? _parseInfo.didPercentEncodeHost : _baseParseInfo?.didPercentEncodeHost ?? false - if percentEncoded { - if didPercentEncodeHost { - return String(encodedHost) - } - guard let decoded = Parser.IDNADecodeHost(encodedHost) else { + guard let encodedHost else { + return nil + } + + func requestedHost() -> String? { + let didPercentEncodeHost = hasAuthority ? _parseInfo.didPercentEncodeHost : _baseParseInfo?.didPercentEncodeHost ?? false + if percentEncoded { + if didPercentEncodeHost { + return encodedHost + } + guard let decoded = Parser.IDNADecodeHost(encodedHost) else { + return encodedHost + } + return Parser.percentEncode(decoded, component: .host) + } else { + if didPercentEncodeHost { + return Parser.percentDecode(encodedHost) + } return encodedHost } - return Parser.percentEncode(decoded, component: .host) + } + + guard let requestedHost = requestedHost() else { + return nil + } + + let isIPLiteral = hasAuthority ? _parseInfo.isIPLiteral : _baseParseInfo?.isIPLiteral ?? false + if isIPLiteral { + // Strip square brackets to be compatible with old URL.host behavior + return String(requestedHost.utf8.dropFirst().dropLast()) } else { - if didPercentEncodeHost { - return Parser.percentDecode(encodedHost) - } - return String(encodedHost) + return requestedHost } } diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index 437a18069..4766c9332 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -642,6 +642,42 @@ final class URLTests : XCTestCase { XCTAssertEqual(url.host, "*.xn--poema-9qae5a.com.br") } + func testURLHostIPLiteralCompatibility() throws { + var url = URL(string: "http://[::]")! + XCTAssertEqual(url.host, "::") + XCTAssertEqual(url.host(), "::") + + url = URL(string: "https://[::1]:433/")! + XCTAssertEqual(url.host, "::1") + XCTAssertEqual(url.host(), "::1") + + url = URL(string: "https://[2001:db8::]/")! + XCTAssertEqual(url.host, "2001:db8::") + XCTAssertEqual(url.host(), "2001:db8::") + + url = URL(string: "https://[2001:db8::]:433")! + XCTAssertEqual(url.host, "2001:db8::") + XCTAssertEqual(url.host(), "2001:db8::") + + url = URL(string: "http://[fe80::a%25en1]")! + XCTAssertEqual(url.absoluteString, "http://[fe80::a%25en1]") + XCTAssertEqual(url.host, "fe80::a%en1") + XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25en1") + XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%en1") + + url = URL(string: "http://[fe80::a%en1]")! + XCTAssertEqual(url.absoluteString, "http://[fe80::a%25en1]") + XCTAssertEqual(url.host, "fe80::a%en1") + XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25en1") + XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%en1") + + url = URL(string: "http://[fe80::a%100%CustomZone]")! + XCTAssertEqual(url.absoluteString, "http://[fe80::a%25100%25CustomZone]") + XCTAssertEqual(url.host, "fe80::a%100%CustomZone") + XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25100%25CustomZone") + XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%100%CustomZone") + } + func testURLTildeFilePath() throws { var url = URL(filePath: "~") // "~" must either be expanded to an absolute path or resolved against a base URL