diff --git a/Sources/FoundationEssentials/URL/URL.swift b/Sources/FoundationEssentials/URL/URL.swift index 4cf6084fd..f0eb086a8 100644 --- a/Sources/FoundationEssentials/URL/URL.swift +++ b/Sources/FoundationEssentials/URL/URL.swift @@ -1123,7 +1123,9 @@ public struct URL: Equatable, Sendable, Hashable { } if let baseScheme = _baseParseInfo.scheme { - result.scheme = String(baseScheme) + // Scheme might be empty, which URL allows for compatibility, + // but URLComponents does not, so we force it internally. + result.forceScheme(String(baseScheme)) } if hasAuthority { @@ -1498,7 +1500,7 @@ public struct URL: Equatable, Sendable, Hashable { } #endif if _baseParseInfo != nil { - return absoluteURL.path(percentEncoded: percentEncoded) + return absoluteURL.relativePath(percentEncoded: percentEncoded) } if percentEncoded { return String(_parseInfo.path) diff --git a/Sources/FoundationEssentials/URL/URLComponents.swift b/Sources/FoundationEssentials/URL/URLComponents.swift index e0fc9d137..d5bbaa7c1 100644 --- a/Sources/FoundationEssentials/URL/URLComponents.swift +++ b/Sources/FoundationEssentials/URL/URLComponents.swift @@ -142,10 +142,12 @@ public struct URLComponents: Hashable, Equatable, Sendable { return nil } - mutating func setScheme(_ newValue: String?) throws { + mutating func setScheme(_ newValue: String?, force: Bool = false) throws { reset(.scheme) - guard Parser.validate(newValue, component: .scheme) else { - throw InvalidComponentError.scheme + if !force { + guard Parser.validate(newValue, component: .scheme) else { + throw InvalidComponentError.scheme + } } _scheme = newValue if encodedHost != nil { @@ -716,6 +718,11 @@ public struct URLComponents: Hashable, Equatable, Sendable { } } + /// Used by `URL` to allow empty scheme for compatibility. + internal mutating func forceScheme(_ scheme: String) { + try? components.setScheme(scheme, force: true) + } + #if FOUNDATION_FRAMEWORK /// Throwing function used by `_NSSwiftURLComponents` to generate an exception for ObjC callers internal mutating func setScheme(_ newValue: String?) throws { diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index ea162af2c..8930afac8 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -946,6 +946,21 @@ final class URLTests : XCTestCase { XCTAssertEqual(schemeOnly.absoluteString, "scheme:foo") } + func testURLEmptySchemeCompatibility() throws { + var url = try XCTUnwrap(URL(string: ":memory:")) + XCTAssertEqual(url.scheme, "") + + let base = try XCTUnwrap(URL(string: "://home")) + XCTAssertEqual(base.host(), "home") + + url = try XCTUnwrap(URL(string: "/path", relativeTo: base)) + XCTAssertEqual(url.scheme, "") + XCTAssertEqual(url.host(), "home") + XCTAssertEqual(url.path, "/path") + XCTAssertEqual(url.absoluteString, "://home/path") + XCTAssertEqual(url.absoluteURL.scheme, "") + } + func testURLComponentsPercentEncodedUnencodedProperties() throws { var comp = URLComponents()