I noticed that there is a problem on iOS 17 that didn't occur on iOS 16. I'm trying to open another app which expects to receive a url (I can't change the expected type). However, when I generate the URL, the 2 dots from of the https are eliminated.
Below is an example code very similar to the original:
extension URL {
public var addAppDomainPrefix : URL? {
let APP_DOMAIN : String = "APPTEST://"
var finalURL: URL? = URL(string: "\(APP_DOMAIN)\(self.absoluteString)")
return finalURL
}
}
In this case if my self.absoluteString is "https://www.google.it" I expect this result:
finalURL -> "APPTEST://https://www.google.it"
while instead I get this
finalURL -> "APPTEST://https//www.google.it"
Reading on various discussions in iOS 17 the way in which a URL is checked has been changed, however I also tried to use the encodingInvalidCharacters parameter but it doesn't work anyway.
Could you help me? Thank you !
The problem is that in a URL, a colon is a reserved character and must not appear (unescaped) in either the domain name or in the first segment of the path. See RFC 3986: Section 3: Syntax Components.
3. Syntax Components
The generic URI syntax consists of a hierarchical sequence of
components referred to as the scheme, authority, path, query, and
fragment.
URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
hier-part = "//" authority path-abempty
/ path-absolute
/ path-rootless
/ path-empty
The scheme and path components are required, though the path may be
empty (no characters). When authority is present, the path must
either be empty or begin with a slash ("/") character. When
authority is not present, the path cannot begin with two slash
characters ("//"). These restrictions result in five different ABNF
rules for a path (Section 3.3), only one of which will match any
given URI reference.
The following are two example URIs and their component parts:
foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
| _____________________|__
/ \ / \
urn:example:animal:ferret:nose
Apple’s URL implementation conforms to this specification.
So, within these constraints, a few observations:
It must be noted that the APPTEST:
URI really should not have the //
given that you do not have an “authority”, but rather are just providing a custom scheme with its own payload. Thus, it really should be APPTEST:…
, not APPTEST://…
.
So, theoretically, you can use the following, which preserves the colon:
extension URL {
public var addAppDomainPrefix: URL? {
let APP_DOMAIN = "APPTEST:"
return URL(string: APP_DOMAIN + absoluteString)
}
}
That appears to do the job for us here:
let google = URL(string: "https://www.google.it")!
if let url = google.addAppDomainPrefix {
logger.debug("\(url)") // APPTEST:https://www.google.it
UIApplication.shared.open(url) { result in
logger.debug("open result: \(result)") // `true`
}
}
FWIW, Apple’s Defining a custom URL scheme for your app does not use //
, either.
If you must include the //
for some reason, then you must conform to RFC 3986 and percent code reserved characters in the authority/host:
extension URL {
public var addAppDomainPrefix: URL? {
let APP_DOMAIN = "APPTEST://"
var components = URLComponents(string: APP_DOMAIN)
components?.encodedHost = absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
return components?.url
}
}
And then you can do:
let google = URL(string: "https://www.google.it")!
if let url = google.addAppDomainPrefix {
logger.debug("\(url)") // APPTEST://https%3A%2F%2Fwww.google.it
UIApplication.shared.open(url) { result in
logger.debug("open result: \(result)") // true
}
}
IMHO, this is an anti-pattern (building a URL, storing some arbitrary payload as the authority/host), but it might be a work-around in this particular case.
I should note that for both of these approaches, canOpen
returns false
, even though open
works (setting aside the idiosyncracies of the app with which you are attempting to communicate). The canOpen
must be doing some additional URL validation that is not necessary.
But, I would contend that, while I cannot defend Apple’s decision to simply omit the colon found in an authority/host in iOS 17 (it should either percent-escape it or just return nil
), their attempt to adhere to RFC 3986 is understandable.
But if that app with which you are interfacing requires the //
, that violates section 3 of RFC 3986. And if the app is not accepting percent escaping of the ://
in the URL payload, that also is a mistake on their part.