URLComponents bug in 18.x

Prior to 18.x, this has worked for years. However, if a port is included, the behavior in 18.x is to return "///some/path". In all previous versions it correctly returns "/some/path".

Is this a bug, or documented change we missed?

func relativeURLString(for urlString: String) -> String? {
        guard var components = URLComponents(string: urlString) else { return nil }
        components.host = nil
        components.scheme = nil
        // WE FOUND IF YOU NIL THE PORT BEFORE HOST AND SCHEME, IT WORKS
        components.port = nil
        return components.string
    }
Answered by DTS Engineer in 821845022

Thanks for filing FB16336663.

Foundation is in the process of some major refactoring [1], so weird problems like this aren’t 100% unexpected. And your code is really exploring an edge case — namely, URLs without a scheme — so that makes things harder.

Still, the fact that the behaviour changes depends on the order in which you set the properties is clearly problematic, and should be look at by the Foundation team.

Note that your code fails, regardless of the system version, if the original URL contains a user name or password. Try this https://mrgumby:opendoor@aHost.com:7443/path/to/module?a=1#blah.

Reading between the lines, it looks like you’re trying to get the path and later components of the URL, so the path, query, and fragment. Here’s how I’d do that:

func relativeURLString(for urlString: String) -> String? {
    guard let components = URLComponents(string: urlString) else { return nil }
    var result = URLComponents()
    result.path = components.path
    result.queryItems = components.queryItems
    result.fragment = components.fragment
    return result.string
}

I think the following a more direct representation of your goal, and it should work regardless of this bug.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] If you’re curious, watch the first half of Swift & Interoperability.

https://youtu.be/%77%6e6C_XEv1Mo

Even returning components.url?.absoluteString in 18.x gives "///some/path"

Can you give me an example of a URL that triggers this failure? That is, what’s the value of the urlString parameter?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Oh, crud, I added code in the Feedback Assistant report (FB16336663) I sent, but forgot to put it here:

  func relativeURLString(for urlString: String) -> String? {

        guard var components = URLComponents(string: urlString) else { return nil }

        ///////

        /// The order of these seems to be important on iOS 18.

        /// port needs to be nil-ed first or the relative URL returned ends up malformed

        components.port = nil

        components.host = nil

        components.scheme = nil

        components.port = nil

        ///////

        return components.string

    }



    func testRelativeURLStringWithPort() {

        let mapping = ModuleMapping(importMap: [:])

        let relativeURL = mapping.relativeURLString(for: "https://aHost.com:7443/path/to/module?a=1#blah")

        let expected = "/path/to/module?a=1#blah"

        XCTAssertEqual(relativeURL, expected)

    }

Thanks for filing FB16336663.

Foundation is in the process of some major refactoring [1], so weird problems like this aren’t 100% unexpected. And your code is really exploring an edge case — namely, URLs without a scheme — so that makes things harder.

Still, the fact that the behaviour changes depends on the order in which you set the properties is clearly problematic, and should be look at by the Foundation team.

Note that your code fails, regardless of the system version, if the original URL contains a user name or password. Try this https://mrgumby:opendoor@aHost.com:7443/path/to/module?a=1#blah.

Reading between the lines, it looks like you’re trying to get the path and later components of the URL, so the path, query, and fragment. Here’s how I’d do that:

func relativeURLString(for urlString: String) -> String? {
    guard let components = URLComponents(string: urlString) else { return nil }
    var result = URLComponents()
    result.path = components.path
    result.queryItems = components.queryItems
    result.fragment = components.fragment
    return result.string
}

I think the following a more direct representation of your goal, and it should work regardless of this bug.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] If you’re curious, watch the first half of Swift & Interoperability.

https://youtu.be/%77%6e6C_XEv1Mo

URLComponents bug in 18.x
 
 
Q