URL(string:) behavior changed with Xcode 15.0 Beta 5

With Xcode 14.3.1 (Swift 5.8.1) I was able to convert URL(string: "127.0.0.1:8000/test") into a valid URL. With Xcode 15.0 Beta 5 (Swift 5.9) the same initializer returns nil. Is this a new behavior or a bug? Does Swift 5.9 already use a pre version of the new open source Foundation package, which already includes URL?

I wonder if this is really a valid URL.
It is not defined what kind of URL is used, e.g.

URL(string: "http://127.0.0.1:8000/test")

The documentation for URL is not really helpful but at NSURL " init(string:)" is some more information:

URLString

The URL string with which to initialize the NSURL object. This URL string must conform to URL format as described in RFC 2396, and must not be nil. This method parses URLString according to RFCs 1738 and 1808.

Just out of curiosity - how were you using the resulting URL value before? Because there is no scheme, there is no way to know what the string means, and indeed the reported components are not what a human would expect:

let url = URL(string: "127.0.0.1:8000/test")!

url.host // nil
url.port // nil
url.path // "127.0.0.1:8000/test"

You also can't make a request to it:

// ❌ throws error: "Unsupported URL"
for try await l in URL(string: "127.0.0.1:8000/test")!.lines {
  print(l)
}

Don't get me wrong - compatibility is very important and behaviour changes need to be made very carefully. But I also wonder what was left that you could do with these URL values.

1 Like

Note to avoid potential confusion that URL is a type in a framework, and so its implementation ships in the operating system. So any change in this behavior would be related to the OS you are running the binary on (i.e. macOS Sonoma), not the version of Swift or Xcode you have.

(an exception to this, which I don't think applies here, is if the initializer were inlinable, in which case it would be related to the version of the SDK you are using, which is tied to Xcode)

6 Likes

Part of the confusion is the the change was originally noted in the Xcode 15 beta 1 release notes rather than any particular OS.

Fixed: For apps linked on or after iOS 17 and aligned OS versions, URL parsing has updated from the obsolete RFC 1738/1808 parsing to the same RFC 3986 parsing as URLComponents. This unifies the parsing behaviors of the URLand URLComponents APIs. Now, URL automatically percent- or IDNA-encodes invalid characters to help create a valid URL.

To check if a urlString is strictly valid according to the RFC, use the new URL(string: urlString, encodingInvalidCharacters: false) initializer. This init leaves all characters as they are and will return nil if urlString is explicitly invalid. (93368104)

Granted, Apple doesn't have a good alternate place to put changes like this without duplicating it in every OS' release notes (which has happened), but some confusion is likely when it shows up in the Xcode release notes itself.

5 Likes

So...passing invalid strings on iOS15~16 will always succeed once encodingInvalidCharacters is only available on iOS17?

I see that in Xcode 15.0.1 passing invalid strings to iOS15~16 has the original behaviour: it will fail when passing invalid characters.

1 Like

Yes, this behavior isn't part of the compiler but the version of the SDK you're running on. Definitely an instance where you need to keep your old escaping logic around conditionally. But it is confusing since the compiler doesn't tell you anything different with an older deployment target. You could also use the new URL(string:encodingInvalidCharacters:) init on iOS 17 to match the older behavior.

1 Like