skip to Main Content

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 !

2

Answers


  1. The short version: if you are using APPTEST: as the URL scheme, then you can’t put https: into the hostname, even of a custom URL, where you’ve defined custom scheme’s format.

    I’ve only used URL framework sparingly in the last few years, but there is a clear trend of making incremental fixes, that this worked in iOS 16 should not lead to an assumption that this should continue to work in iOS 17.

    Having a ":" in a hostname is problematic, doing a DNS lookup on that is not going to work, esp. in the real world.

    Login or Signup to reply.
  2. 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:

    1. 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.

    2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search