ad_library {
This is a Tcl implementation of various URI-related operations.
Uniform Resource Identifiers (URIs) are defined by
RFC 2396 as
"a simple and extensible means for identifying a resource" and
include both Uniform Resource Locators (URLs) and
Uniform Resource Names (URNs). URIs are developed by the
World Wide Consortium (W3C), see
their addressing page
for more information.
It is made available under the terms of the GNU License.
See the GNU website for more information.
@author Aaron Swartz
@creation-date 2001-2-13
}
ad_proc as_parse_uri {uri_in} {
Takes an URI (including URLs) and returns a listified array of its parts:
scheme, authority, path, query and fragment. An entry will be
missing if it was not included in the URI.
For example, a URI of http://example.org/q#z
would return
scheme http authority example.org path /q fragment z.
Notice that query was not included in the the returned list, since there was
no query part to the URI.
} {
regexp -indices {\A(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?} $uri_in {} {} scheme {} authority path {} query {} fragment
foreach name {scheme authority path query fragment} {
if {[set $name] != "-1 -1"} {
set uri($name) [string range $uri_in [lindex [set $name] 0] [lindex [set $name] 1]]
}
}
return [array get uri]
}
ad_proc as_parse_uri_test {} {
Returns 1 if as_parse_uri works, otherwise throws an error.
} {
set uri_l [as_parse_uri http://www.ics.uci.edu/pub/ietf/uri/#Related]
array set uri $uri_l
foreach {name value} {
scheme "http"
authority "www.ics.uci.edu"
path "/pub/ietf/uri/"
query NULL
fragment "Related"
} {
if {$value != "NULL" && ![string equal $uri($name) $value]} {
error "$name broken: is \"$uri($name)\"; should be $value"
} elseif {$value == "NULL" && [info exists uri($name)]} {
error "$name broken: is \"$uri($name)\"; should be NULL"
}
}
return 1
}
ad_proc as_resolve_relative_uri {base target} {
Takes a base URI (including URLs) and a target URI
and returns the absolute URI.
For example, a base of http://example.org/q/y and a target of
../foo would return http://example.org/foo.
} {
# 1: Parse URI
set target_l [as_parse_uri $target]
array set target_uri $target_l
set base_l [as_parse_uri $base]
array set base_uri $base_l
# 2: Detect null reference
if {[string equal $target_uri(path) ""] && ![info exists target_uri(scheme)] && ![info exists target_uri(authority)] && ![info exists target_uri(query)]} {
if {[info exists target_uri(fragment)]} {
return $base#$target_uri(fragment)
}
return $base
}
set path $target_uri(path)
if {[info exists target_uri(query)]} {set query $target_uri(query)}
if {[info exists target_uri(fragment)]} {set fragment $target_uri(fragment)}
if {[info exists target_uri(authority)]} {set authority $target_uri(authority)}
# 3: Detect absolute URI
if {[info exists target_uri(scheme)]} {
return $target
} {
set scheme $base_uri(scheme)
}
# 4: Detect authority
if {![info exists target_uri(authority)]} {
set authority $base_uri(authority)
# 5: check for starting with a slash
if {[string index $target_uri(path) 0] != "/"} {
# 6: Resolve relative-path reference
# a: Copy all but last segment
set buffer ""
if {[info exists base_uri(path)]} {
set buffer [string range $base_uri(path) 0 [string last / $base_uri(path)]]
}
# b: Append target's path
append buffer $target_uri(path)
# c and d: remove . path segments
set buffer [split $buffer /]
set i 0
foreach segment $buffer {
incr i
if {$segment != "."} {lappend newbuffer $segment} {
if {$i == 1 || $i == [llength $buffer]} {
lappend newbuffer ""
}
}
}
# e and f: deal with .. path segments
set i 0
foreach segment $newbuffer {
if {$segment == ".."} {
if {$i <= 0 || $i == [expr [llength $newbuffer] - 1]} {
set newbuffer [lreplace $newbuffer [expr $i - 1] $i ""]
} {
set newbuffer [lreplace $newbuffer [expr $i - 1] $i]
}
incr i -2 ;# adjust i for the items we just deleted
}
incr i
}
# g: dealing with extra .. path segements
# we don't really worry about this -- we just delete them all
# but we do have to make sure we don't delete the root:
if {[lindex $newbuffer 0] != ""} {
set newbuffer [lreplace $newbuffer 0 0 "" [lindex $newbuffer 0]]
}
# h: set the path
set path [join $newbuffer /]
}
}
# 7: recreate URI
set result ""
if {[info exists scheme]} {
append result "$scheme:"
}
if {[info exists authority]} {
append result "//$authority"
}
append result $path
if {[info exists query]} {
append result "?$query"
}
if {[info exists fragment]} {
append result #$fragment
}
return $result
}
ad_proc as_resolve_relative_uri_test {} {
Tests as_resolve_relative_uri. Returns a 1 if it works, throws an error if it doesn't.
} {
foreach {target result} {
g:h g:h
g http://a/b/c/g
./g http://a/b/c/g
g/ http://a/b/c/g/
/g http://a/g
//g http://g
?y http://a/b/c/?y
g?y http://a/b/c/g?y
#s http://a/b/c/d;p?q#s
g#s http://a/b/c/g#s
g?y#s http://a/b/c/g?y#s
;x http://a/b/c/;x
g;x http://a/b/c/g;x
g;x?y#s http://a/b/c/g;x?y#s
. http://a/b/c/
./ http://a/b/c/
.. http://a/b/
../ http://a/b/
../g http://a/b/g
../.. http://a/
../../ http://a/
../../g http://a/g
"" http://a/b/c/d;p?q
../../../g http://a/g
../../../../g http://a/g
/./g http://a/./g
/../g http://a/../g
g. http://a/b/c/g.
.g http://a/b/c/.g
g.. http://a/b/c/g..
..g http://a/b/c/..g
./../g http://a/b/g
./g/. http://a/b/c/g/
g/./h http://a/b/c/g/h
g/../h http://a/b/c/h
g;x=1/./y http://a/b/c/g;x=1/y
g;x=1/../y http://a/b/c/y
g?y/./x http://a/b/c/g?y/./x
g?y/../x http://a/b/c/g?y/../x
g#s/./x http://a/b/c/g#s/./x
g#s/../x http://a/b/c/g#s/../x
} {
set my_result [as_resolve_relative_uri {http://a/b/c/d;p?q} $target]
if {$my_result != $result} {
error "with target \"$target\" expected \"$result\" but got \"$my_result\""
}
}
return 1
}