1
0
mirror of https://github.com/golang/go synced 2024-11-21 16:04:45 -07:00

http: support for relative URLs

R=rsc, adg
CC=golang-dev
https://golang.org/cl/4002041
This commit is contained in:
Brad Fitzpatrick 2011-01-19 15:13:42 -05:00 committed by Russ Cox
parent a4f6ed3574
commit f96c1d076a
2 changed files with 210 additions and 94 deletions

View File

@ -114,62 +114,6 @@ func shouldEscape(c byte, mode encoding) bool {
return true
}
// CanonicalPath applies the algorithm specified in RFC 2396 to
// simplify the path, removing unnecessary . and .. elements.
func CanonicalPath(path string) string {
buf := []byte(path)
a := buf[0:0]
// state helps to find /.. ^.. ^. and /. patterns.
// state == 1 - prev char is '/' or beginning of the string.
// state > 1 - prev state > 0 and prev char was '.'
// state == 0 - otherwise
state := 1
cnt := 0
for _, v := range buf {
switch v {
case '/':
s := state
state = 1
switch s {
case 2:
a = a[0 : len(a)-1]
continue
case 3:
if cnt > 0 {
i := len(a) - 4
for ; i >= 0 && a[i] != '/'; i-- {
}
a = a[0 : i+1]
cnt--
continue
}
default:
if len(a) > 0 {
cnt++
}
}
case '.':
if state > 0 {
state++
}
default:
state = 0
}
l := len(a)
a = a[0 : l+1]
a[l] = v
}
switch {
case state == 2:
a = a[0 : len(a)-1]
case state == 3 && cnt > 0:
i := len(a) - 4
for ; i >= 0 && a[i] != '/'; i-- {
}
a = a[0 : i+1]
}
return string(a)
}
// URLUnescape unescapes a string in ``URL encoded'' form,
// converting %AB into the byte 0xAB and '+' into ' ' (space).
@ -553,3 +497,99 @@ func EncodeQuery(m map[string][]string) string {
}
return strings.Join(parts, "&")
}
// resolvePath applies special path segments from refs and applies
// them to base, per RFC 2396.
func resolvePath(basepath string, refpath string) string {
base := strings.Split(basepath, "/", -1)
refs := strings.Split(refpath, "/", -1)
if len(base) == 0 {
base = []string{""}
}
for idx, ref := range refs {
switch {
case ref == ".":
base[len(base)-1] = ""
case ref == "..":
newLen := len(base) - 1
if newLen < 1 {
newLen = 1
}
base = base[0:newLen]
base[len(base)-1] = ""
default:
if idx == 0 || base[len(base)-1] == "" {
base[len(base)-1] = ref
} else {
base = append(base, ref)
}
}
}
return strings.Join(base, "/")
}
// IsAbs returns true if the URL is absolute.
func (url *URL) IsAbs() bool {
return url.Scheme != ""
}
// ParseURL parses a URL in the context of a base URL. The URL in ref
// may be relative or absolute. ParseURL returns nil, err on parse
// failure, otherwise its return value is the same as ResolveReference.
func (base *URL) ParseURL(ref string) (*URL, os.Error) {
refurl, err := ParseURL(ref)
if err != nil {
return nil, err
}
return base.ResolveReference(refurl), nil
}
// ResolveReference resolves a URI reference to an absolute URI from
// an absolute base URI, per RFC 2396 Section 5.2. The URI reference
// may be relative or absolute. ResolveReference always returns a new
// URL instance, even if the returned URL is identical to either the
// base or reference. If ref is an absolute URL, then ResolveReference
// ignores base and returns a copy of ref.
func (base *URL) ResolveReference(ref *URL) *URL {
url := new(URL)
switch {
case ref.IsAbs():
*url = *ref
default:
// relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
*url = *base
if ref.RawAuthority != "" {
// The "net_path" case.
url.RawAuthority = ref.RawAuthority
url.Host = ref.Host
url.RawUserinfo = ref.RawUserinfo
}
switch {
case url.OpaquePath:
url.Path = ref.Path
url.RawPath = ref.RawPath
url.RawQuery = ref.RawQuery
case strings.HasPrefix(ref.Path, "/"):
// The "abs_path" case.
url.Path = ref.Path
url.RawPath = ref.RawPath
url.RawQuery = ref.RawQuery
default:
// The "rel_path" case.
path := resolvePath(base.Path, ref.Path)
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
url.Path = path
url.RawPath = url.Path
url.RawQuery = ref.RawQuery
if ref.RawQuery != "" {
url.RawPath += "?" + url.RawQuery
}
}
url.Fragment = ref.Fragment
}
url.Raw = url.String()
return url
}

View File

@ -510,44 +510,6 @@ func TestURLEscape(t *testing.T) {
}
}
type CanonicalPathTest struct {
in string
out string
}
var canonicalTests = []CanonicalPathTest{
{"", ""},
{"/", "/"},
{".", ""},
{"./", ""},
{"/a/", "/a/"},
{"a/", "a/"},
{"a/./", "a/"},
{"./a", "a"},
{"/a/../b", "/b"},
{"a/../b", "b"},
{"a/../../b", "../b"},
{"a/.", "a/"},
{"../.././a", "../../a"},
{"/../.././a", "/../../a"},
{"a/b/g/../..", "a/"},
{"a/b/..", "a/"},
{"a/b/.", "a/b/"},
{"a/b/../../../..", "../.."},
{"a./", "a./"},
{"/../a/b/../../../", "/../../"},
{"../a/b/../../../", "../../"},
}
func TestCanonicalPath(t *testing.T) {
for _, tt := range canonicalTests {
actual := CanonicalPath(tt.in)
if tt.out != actual {
t.Errorf("CanonicalPath(%q) = %q, want %q", tt.in, actual, tt.out)
}
}
}
type UserinfoTest struct {
User string
Password string
@ -597,3 +559,117 @@ func TestEncodeQuery(t *testing.T) {
}
}
}
var resolvePathTests = []struct {
base, ref, expected string
}{
{"a/b", ".", "a/"},
{"a/b", "c", "a/c"},
{"a/b", "..", ""},
{"a/", "..", ""},
{"a/", "../..", ""},
{"a/b/c", "..", "a/"},
{"a/b/c", "../d", "a/d"},
{"a/b/c", ".././d", "a/d"},
{"a/b", "./..", ""},
{"a/./b", ".", "a/./"},
{"a/../", ".", "a/../"},
{"a/.././b", "c", "a/.././c"},
}
func TestResolvePath(t *testing.T) {
for _, test := range resolvePathTests {
got := resolvePath(test.base, test.ref)
if got != test.expected {
t.Errorf("For %q + %q got %q; expected %q", test.base, test.ref, got, test.expected)
}
}
}
var resolveReferenceTests = []struct {
base, rel, expected string
}{
// Absolute URL references
{"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"},
{"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"},
{"http://foo.com/bar", "mailto:foo@example.com", "mailto:foo@example.com"},
// Path-absolute references
{"http://foo.com/bar", "/baz", "http://foo.com/baz"},
{"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"},
{"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"},
// Scheme-relative
{"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"},
// Path-relative references:
// ... current directory
{"http://foo.com", ".", "http://foo.com/"},
{"http://foo.com/bar", ".", "http://foo.com/"},
{"http://foo.com/bar/", ".", "http://foo.com/bar/"},
// ... going down
{"http://foo.com", "bar", "http://foo.com/bar"},
{"http://foo.com/", "bar", "http://foo.com/bar"},
{"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"},
// ... going up
{"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"},
{"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"},
{"http://foo.com/bar", "..", "http://foo.com/"},
{"http://foo.com/bar/baz", "./..", "http://foo.com/"},
// "." and ".." in the base aren't special
{"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/./dotdot/../baz"},
// Triple dot isn't special
{"http://foo.com/bar", "...", "http://foo.com/..."},
// Fragment
{"http://foo.com/bar", ".#frag", "http://foo.com/#frag"},
}
func TestResolveReference(t *testing.T) {
mustParseURL := func(url string) *URL {
u, err := ParseURLReference(url)
if err != nil {
t.Fatalf("Expected URL to parse: %q, got error: %v", url, err)
}
return u
}
for _, test := range resolveReferenceTests {
base := mustParseURL(test.base)
rel := mustParseURL(test.rel)
url := base.ResolveReference(rel)
urlStr := url.String()
if urlStr != test.expected {
t.Errorf("Resolving %q + %q != %q; got %q", test.base, test.rel, test.expected, urlStr)
}
}
// Test that new instances are returned.
base := mustParseURL("http://foo.com/")
abs := base.ResolveReference(mustParseURL("."))
if base == abs {
t.Errorf("Expected no-op reference to return new URL instance.")
}
barRef := mustParseURL("http://bar.com/")
abs = base.ResolveReference(barRef)
if abs == barRef {
t.Errorf("Expected resolution of absolute reference to return new URL instance.")
}
// Test the convenience wrapper too
base = mustParseURL("http://foo.com/path/one/")
abs, _ = base.ParseURL("../two")
expected := "http://foo.com/path/two"
if abs.String() != expected {
t.Errorf("ParseURL wrapper got %q; expected %q", abs.String(), expected)
}
_, err := base.ParseURL("")
if err == nil {
t.Errorf("Expected an error from ParseURL wrapper parsing an empty string.")
}
}