Skip to content

Commit

Permalink
Preserve slashes in URIs without authority (elixir-lang#9632)
Browse files Browse the repository at this point in the history
Old behavior:

    to_string(URI.parse("file:///path")) == "file:/path"

New behavior:

    to_string(URI.parse("file:///path")) == "file:///path"
  • Loading branch information
ericmj authored Dec 6, 2019
1 parent e1b14ca commit 40799f2
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 10 deletions.
40 changes: 34 additions & 6 deletions lib/elixir/lib/uri.ex
Original file line number Diff line number Diff line change
Expand Up @@ -454,18 +454,38 @@ defmodule URI do

def parse(string) when is_binary(string) do
# From https://tools.ietf.org/html/rfc3986#appendix-B
# Parts: 12 3 4 5 6 7 8 9
regex = ~r{^(([a-z][a-z0-9\+\-\.]*):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?}i

parts = Regex.run(regex, string)

destructure [_, _, scheme, _, authority, path, query_with_question_mark, _, _, fragment],
destructure [
_full,
# 1
_scheme_with_colon,
# 2
scheme,
# 3
authority_with_slashes,
# 4
_authority,
# 5
path,
# 6
query_with_question_mark,
# 7
_query,
# 8
_fragment_with_hash,
# 9
fragment
],
parts

scheme = nillify(scheme)
authority = nillify(authority)
path = nillify(path)
query = nillify_query(query_with_question_mark)
{userinfo, host, port} = split_authority(authority)
{authority, userinfo, host, port} = split_authority(authority_with_slashes)

scheme = scheme && String.downcase(scheme)
port = port || (scheme && default_port(scheme))
Expand All @@ -486,16 +506,24 @@ defmodule URI do
defp nillify_query(_other), do: nil

# Split an authority into its userinfo, host and port parts.
defp split_authority(string) do
defp split_authority("") do
{nil, nil, nil, nil}
end

defp split_authority("//") do
{"", nil, "", nil}
end

defp split_authority("//" <> authority) do
regex = ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/
components = Regex.run(regex, string || "")
components = Regex.run(regex, authority)

destructure [_, _, userinfo, host, _, port], components
userinfo = nillify(userinfo)
host = if nillify(host), do: host |> String.trim_leading("[") |> String.trim_trailing("]")
port = if nillify(port), do: String.to_integer(port)

{userinfo, host, port}
{authority, userinfo, host, port}
end

# Regex.run returns empty strings sometimes. We want
Expand Down
11 changes: 7 additions & 4 deletions lib/elixir/test/elixir/uri_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,13 @@ defmodule URITest do
test "works with \"file\" scheme" do
expected_uri = %URI{
scheme: "file",
host: nil,
host: "",
path: "/foo/bar/baz",
userinfo: nil,
query: nil,
fragment: nil,
port: nil,
authority: nil
authority: ""
}

assert URI.parse("file:///foo/bar/baz") == expected_uri
Expand Down Expand Up @@ -179,8 +179,8 @@ defmodule URITest do
test "works with LDAP scheme" do
expected_uri = %URI{
scheme: "ldap",
host: nil,
authority: nil,
host: "",
authority: "",
userinfo: nil,
path: "/dc=example,dc=com",
query: "?sub?(givenName=John)",
Expand Down Expand Up @@ -331,6 +331,9 @@ defmodule URITest do
assert to_string(URI.parse("http://google.com")) == "http://google.com"
assert to_string(URI.parse("http://google.com:443")) == "http://google.com:443"
assert to_string(URI.parse("https://google.com:443")) == "https://google.com"
assert to_string(URI.parse("file:/path")) == "file:/path"
assert to_string(URI.parse("file:///path")) == "file:///path"
assert to_string(URI.parse("file://///path")) == "file://///path"
assert to_string(URI.parse("http://lol:[email protected]")) == "http://lol:[email protected]"
assert to_string(URI.parse("http://google.com/elixir")) == "http://google.com/elixir"
assert to_string(URI.parse("http://google.com?q=lol")) == "http://google.com?q=lol"
Expand Down

0 comments on commit 40799f2

Please sign in to comment.