1

When I do

let baseUrl = URL(string: "ftp://www.foobar.com/foo/bar/")
let finalUrl = URL(string: "//barfoo.com/bar/foo", relativeTo: baseUrl)
print(finalUrl?.absoluteString ?? "ooops")

it prints ftp://barfoo.com/bar/foo

Is this expected behaviour? Is this documented somewhere?

[EDIT] I was just wondering, because I'd expect that all the stuff, including server and path would be taken from the base url, like here:

let baseUrl = URL(string: "ftp://www.foobar.com/foo/bar/")
let finalUrl = URL(string: "boo/far", relativeTo: baseUrl)
print(finalUrl?.absoluteString ?? "ooops")

which prints ftp://www.foobar.com/foo/bar/boo/far.

Why is the server and everything else from the base url being ignored in the first example? Is that due to the // at the beginning? And is this part of some RFC or something or documented somewhere else?

Kai Huppmann
  • 10,705
  • 6
  • 47
  • 78
  • 1
    This looks correct. What were you expecting this to return? What do you mean by "replace only the protocol?" By "protocol" I assume you mean scheme, and that's the one thing that **isn't** replaced. – Rob Napier Aug 08 '23 at 14:11
  • Thanks @RobNapier. Yes I mean the scheme (which will be interpreted in a way that will end up in using a certain protocol). And yes, it's a bit confusing ... With "replace" actually I mean "added". What I found confusing is, that the only thing taken from the baseUrl `ftp://www.foobar.com/foo/bar` is the `ftp:`, while "relativeTo" suggests that the full server and path will be used from the baseUrl. I will clarify that with an edit. – Kai Huppmann Aug 08 '23 at 15:05

1 Answers1

3

As you note, this is due to the // at the beginning. From the URI RFC, the format of a URI is:

  URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
  hier-part   = "//" authority path-abempty
              / path-absolute
              / path-rootless
              / path-empty

When you begin your URI with //, you're indicating that it should replace the authority and path, which it does. As described in Section 3.3, a path-absolute cannot begin with //:

                / path-absolute   ; begins with "/" but not "//"

If your intention is to replace the path without modifying the authority (userinfo, host, port), then you can pass an absolute path (beginning with /) rather than an authority and path (beginning with //):

let finalUrl = URL(string: "/barfoo.com/bar/foo", relativeTo: baseUrl)
// ftp://www.foobar.com/barfoo.com/bar/foo

And if you want to append a path, you can pass a rootless path (not beginning with a /):

let finalUrl = URL(string: "barfoo.com/bar/foo", relativeTo: baseUrl)
// ftp://www.foobar.com/foo/barfoo.com/bar/foo

For operations more complex that this, you should definitely look at URLComponents instead, which allows you to query and modify parts more explicitly and safely.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    Thanks very much. As I found out he URI RFC, the. Section 5.2.2 describes an algorithm (https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.2), to construct a target URI from base uri and reference uri, which exactly matches, what we see here, in your and my examples. – Kai Huppmann Aug 08 '23 at 21:00