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 5 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
87 changes: 86 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 @@ -518,6 +519,90 @@ function Base.joinpath(uri::URI, parts::String...)
return URI(uri; path=normpath(path))
end

"""
resolvereference(base, ref)
kernelmethod marked this conversation as resolved.
Show resolved Hide resolved

Resolve a URI reference `ref` relative to the absolute base URI `base`,
complying with RFC 3986 Section 5.2. `base` and `ref` should both be
of type `Union{URI,AbstractString}`.
kernelmethod marked this conversation as resolved.
Show resolved Hide resolved

If `ref` is an absolute URI, then this function just returns a copy
of `ref`.
kernelmethod marked this conversation as resolved.
Show resolved Hide resolved

# 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), ref)
resolvereference(base::URI, ref) = resolvereference(base, URI(ref))
kernelmethod marked this conversation as resolved.
Show resolved Hide resolved

"""
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 __init__()
Threads.resize_nthreads!(uri_reference_regex)
foreach(x -> Base.compile(x.re), uri_reference_regex)
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