Skip to content

Commit

Permalink
Add resolvereference to resolve URI references relative to a base URI (
Browse files Browse the repository at this point in the history
…#19)

Adds a new function, `resolvereference`, that resolves references
between a base URI and a reference URI. This function attempts to comply
with RFC 3986 Section 5.2 (https://tools.ietf.org/html/rfc3986#section-5.2).

fixes #18, fixes JuliaWeb/HTTP.jl#435, fixes JuliaWeb/HTTP.jl#626
  • Loading branch information
kernelmethod committed Apr 30, 2021
1 parent de58768 commit d481cf3
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 1 deletion.
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

0 comments on commit d481cf3

Please sign in to comment.