Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a function to resolve URI references relative to a base URI #19

Merged
merged 7 commits into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ absuri
escapeuri
unescapeuri
escapepath
resolvereference
URIs.splitpath
Base.isvalid(::URI)
```
Expand Down
85 changes: 84 additions & 1 deletion src/URIs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ module URIs

export URI,
queryparams, absuri,
escapeuri, unescapeuri, escapepath
escapeuri, unescapeuri, escapepath,
resolvereference

import Base.==

Expand Down Expand Up @@ -523,6 +524,88 @@ function Base.joinpath(uri::URI, parts::String...)
return URI(uri; path=normpath(path))
end

"""
resolvereference(base::Union{URI,AbstractString}, ref::Union{URI,AbstractString}) -> URI

Resolve a URI reference `ref` relative to the absolute base URI `base`,
complying with [RFC 3986 Section 5.2](https://tools.ietf.org/html/rfc3986#section-5.2).

If `ref` is an absolute URI, return `ref` unchanged.

# Examples

```jldoctest; setup = :(using URIs)
julia> u = resolvereference("http://example.org/foo/bar/", "/baz/")
URI("http://example.org/baz/")

julia> resolvereference(u, "./hello/world")
URI("http://example.org/baz/hello/world")

julia> resolvereference(u, "http://localhost:8000")
URI("http://localhost:8000")
```
"""
function resolvereference(base::URI, ref::URI)
# In the case where the second URI is absolute, we just return the
# reference URI. Refer to https://tools.ietf.org/html/rfc3986#section-5.2.2
#
# We also default to just returning the reference when the base URI is
# non-absolute.
if isempty(base.scheme) || !isempty(ref.scheme)
return ref
end

host, port, path, query = if !isempty(ref.host)
ref.host, ref.port, ref.path, ref.query
else
path, query = if isempty(ref.path)
base.path, isempty(ref.query) ? base.query : ref.query
else
path = startswith(ref.path, "/") ? ref.path : resolveref_merge(base, ref)
path, ref.query
end
base.host, base.port, path, query
end

path = normpath(path)
scheme = base.scheme
fragment = ref.fragment
userinfo = isempty(ref.userinfo) ? base.userinfo : ref.userinfo

URI(;
scheme=scheme,
userinfo=userinfo,
host=host,
port=port,
path=path,
query=query,
fragment=fragment
)
end

resolvereference(base, ref) = resolvereference(URI(base), URI(ref))

"""
resolveref_merge(base, ref)

Implementation of the "merge" routine described in RFC 3986 Sec. 5.2.3 for merging
a relative-path reference with the path of the base URI.
"""
function resolveref_merge(base, ref)
if !isempty(base.host) && isempty(base.path)
"/" * ref.path
else
last_slash = findprev("/", base.path, lastindex(base.path))
if last_slash === nothing
ref.path
else
last_slash = first(last_slash)
base.path[1:last_slash] * ref.path
end
end
end


function access_threaded(f, v::Vector)
tid = Threads.threadid()
0 < tid <= length(v) || _length_assert()
Expand Down
51 changes: 51 additions & 0 deletions test/uri.jl
Original file line number Diff line number Diff line change
Expand Up @@ -547,4 +547,55 @@ urltests = URLTest[
@test joinpath(URIs.URI("http://a.b.c/"), "b", "c") == URI("http://a.b.c/b/c")
@test joinpath(URIs.URI("http://a.b.c"), "b", "c") == URI("http://a.b.c/b/c")
end

@testset "resolvereference" begin
# Tests for resolving URI references, as defined in Section 5.4

# Perform some basic tests resolving absolute and relative references to a base URI
uri = URI("http://example.org/foo/bar/")
@test resolvereference(uri, "/baz") == URI("http://example.org/baz")
@test resolvereference(uri, "baz/") == URI("http://example.org/foo/bar/baz/")
@test resolvereference(uri, "../baz/") == URI("http://example.org/foo/baz/")

# If the base URI's path doesn't end with a /, we handle relative URIs a little differently
uri = URI("http://example.org/foo/bar")
@test resolvereference(uri, "baz") == URI("http://example.org/foo/baz")
@test resolvereference(uri, "../baz") == URI("http://example.org/baz")

# If the second URI is absolute, or the first URI isn't, we should just return the
# second URI.
@test resolvereference("http://www.example.org", "http://example.com") == URI("http://example.com")
@test resolvereference("http://example.org/foo", "http://example.org/bar") == URI("http://example.org/bar")
@test resolvereference("/foo", "/bar/baz") == URI("/bar/baz")

# "Normal examples" specified in Section 5.4.1
base = URI("http://a/b/c/d;p?q")
@test resolvereference(base, "g:h") == URI("g:h")
@test resolvereference(base, "g") == URI("http://a/b/c/g")
@test resolvereference(base, "./g") == URI("http://a/b/c/g")
@test resolvereference(base, "g/") == URI("http://a/b/c/g/")
@test resolvereference(base, "/g") == URI("http://a/g")
@test resolvereference(base, "//g") == URI("http://g")
@test resolvereference(base, "?y") == URI("http://a/b/c/d;p?y")
@test resolvereference(base, "g?y") == URI("http://a/b/c/g?y")
@test resolvereference(base, "#s") == URI("http://a/b/c/d;p?q#s")
@test resolvereference(base, "g#s") == URI("http://a/b/c/g#s")
@test resolvereference(base, "g?y#s") == URI("http://a/b/c/g?y#s")
@test resolvereference(base, ";x") == URI("http://a/b/c/;x")
@test resolvereference(base, "g;x") == URI("http://a/b/c/g;x")
@test resolvereference(base, "g;x?y#s") == URI("http://a/b/c/g;x?y#s")
@test resolvereference(base, "") == URI("http://a/b/c/d;p?q")
@test resolvereference(base, ".") == URI("http://a/b/c/")
@test resolvereference(base, "./") == URI("http://a/b/c/")
@test resolvereference(base, "..") == URI("http://a/b/")
@test resolvereference(base, "../") == URI("http://a/b/")
@test resolvereference(base, "../g") == URI("http://a/b/g")
@test resolvereference(base, "../..") == URI("http://a/")
@test resolvereference(base, "../../") == URI("http://a/")
@test resolvereference(base, "../../g") == URI("http://a/g")

# "Abnormal examples" specified in Section 5.4.2
@test resolvereference(base, "../../../g") == URI("http://a/g")
@test resolvereference(base, "../../../../g") == URI("http://a/g")
end
end