mirror of
https://github.com/golang/go
synced 2024-11-22 03:14:41 -07:00
http: support for relative URLs
R=rsc, adg CC=golang-dev https://golang.org/cl/4002041
This commit is contained in:
parent
a4f6ed3574
commit
f96c1d076a
@ -114,62 +114,6 @@ func shouldEscape(c byte, mode encoding) bool {
|
|||||||
return true
|
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,
|
// URLUnescape unescapes a string in ``URL encoded'' form,
|
||||||
// converting %AB into the byte 0xAB and '+' into ' ' (space).
|
// converting %AB into the byte 0xAB and '+' into ' ' (space).
|
||||||
@ -553,3 +497,99 @@ func EncodeQuery(m map[string][]string) string {
|
|||||||
}
|
}
|
||||||
return strings.Join(parts, "&")
|
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
|
||||||
|
}
|
||||||
|
@ -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 {
|
type UserinfoTest struct {
|
||||||
User string
|
User string
|
||||||
Password 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.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user