swifturlnsurlrfc3986nsurlcomponents

What kind of URL is not conforming to RFC 3986 but is conforming to RFC 1808, RFC 1738, and RFC 2732?


The doc of URLComponents.init(url:resolvingAgainstBaseURL:) says:

Returns the initialized URL components object, or nil if the URL could not be parsed.

Knowing that:

I assume that the initialization of URLComponents will fail when the URL is conforming to RFC 1808/1738/2732 but not RFC 3986. What kind of URL is that? Any example?

The only hint I have so far as a difference may be related to different reserved characters?


Solution

  • [update since iOS 17 and macOS 14]

    Since iOS 17, URL/NSURL now conforms to the RFC 3986 and no more to the obsolete RFC 1738/1808. Consequently, there are no more examples where URL would build successfully but where URLComponents would fail.

    Answer for versions BEFORE iOS 17 / macOS 14

    Let's explore it from its source code, as Swift Foundation is open-source.

    1. The URLComponents initializer is implemented in apple/swift – URLComponents.swift and apple/swift-corelibs-foundation – URLComponents.swift and simply calls the initializer of NSURLComponents.

    2. The NSURLComponents initializer is implemented in apple/swift-corelibs-foundation – NSURL.swift and simply calls _CFURLComponentsCreateWithURL.

    3. _CFURLComponentsCreateWithURL is implemented in apple/swift-corelibs-foundation – CFURLComponents.c and does:

    1. CFURLCopyAbsoluteURL is implemented in apple/swift-corelibs-foundation – CFURL.c and only fails for:

       #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
           if ( base && CFURLIsFileReferenceURL(base) && !CFURLHasDirectoryPath(base) ) {
               // 16695827 - If the base URL is a file reference URL which doesn't end with a slash, we have to convert it to a file path URL before we can make it absolute.
               base = CFURLCreateFilePathURL(alloc, base, NULL);
               if ( !base ) {
                   // could not convert file reference URL to file path URL -- fail will NULL
                   return NULL;
               }
           }
       #endif
      

    The implementation of CFURLCreateFilePathURL is in opensource.apple.com/source/CF – CFURL.c, and my understanding is that it will only fail if there is no scheme or no path, which shouldn't be possible as we previously tested for a file scheme or file existence with CFURLIsFileReferenceURL.

    1. _CFURIParserParseURIReference is implemented in apple/swift-corelibs-foundation – CFURLComponents_URIParser.c and will only fail if the URL length is more than 2 GB, which I believe is unrelated to RFC specifications.

    2. _CFURIParserURLStringIsValid will essentially call _CFURIParserValidateComponent for each component and fail for invalid characters or escape sequences. This is possibly the most relevant part.

    Now, with a bit of experiments, we know we need a scheme (for instance, https:// or simply a://) and we play with the reserved characters to come up with examples such as:

    // OK
    let url = URL(string: "a://@@")!
    // CRASH
    let components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
    

    Trying the alternative initializer of URLComponents will also fail, so don't try to think it's different:

    // CRASH
    let components = URLComponents(string: url.absoluteString)!
    

    Conclusion (before iOS 17)

    "a://@@" is an example of valid NSURL but invalid RFC 3986.

    On a side note, some Swift people seem to wish for the future to unify the support of URL and URLComponents (no more RFC differences) as seen in URL.swift:

    // Future implementation note:
    // NSURL (really CFURL, which provides its implementation) has quite a few quirks in its processing of some more esoteric (and some not so esoteric) strings. We would like to move much of this over to the more modern NSURLComponents, but binary compat concerns have made this difficult.
    // Hopefully soon, we can replace some of the below delegation to NSURL with delegation to NSURLComponents instead. It cannot be done piecemeal, because otherwise we will get inconsistent results from the API.

    I'm not sure how they plan to do this, as it would mean that either URL(string: "a://@@") would fail or URLComponents(string: "a://@@") would succeed.

    Well history shows that, since iOS 17, URL(string: "a://@@") is now returning nil.