]> Cypherpunks repositories - gostls13.git/commitdiff
net/url: cleaned up URL interface (v2)
authorGustavo Niemeyer <gustavo@niemeyer.net>
Tue, 17 Jan 2012 02:49:05 +0000 (00:49 -0200)
committerGustavo Niemeyer <gustavo@niemeyer.net>
Tue, 17 Jan 2012 02:49:05 +0000 (00:49 -0200)
Duplicated fields from URL were dropped so that its behavior
is simple and expected when being stringified and when being
operated by packages like http. Most of the preserved fields
are in unencoded form, except for RawQuery which continues to
exist and be more easily handled via url.Query().

The RawUserinfo field was also replaced since it wasn't practical
to use and had limitations when operating with empty usernames
and passwords which are allowed by the RFC. In its place the
Userinfo type was introduced and made accessible through the
url.User and url.UserPassword functions.

What was previous built as:

        url.URL{RawUserinfo: url.EncodeUserinfo("user", ""), ...}

Is now built as:

        url.URL{User: url.User("user"), ...}

R=rsc, bradfitz, gustavo
CC=golang-dev
https://golang.org/cl/5498076

13 files changed:
src/pkg/net/http/cgi/host.go
src/pkg/net/http/client.go
src/pkg/net/http/httputil/dump.go
src/pkg/net/http/httputil/reverseproxy.go
src/pkg/net/http/readrequest_test.go
src/pkg/net/http/request.go
src/pkg/net/http/requestwrite_test.go
src/pkg/net/http/serve_test.go
src/pkg/net/http/transport.go
src/pkg/net/url/url.go
src/pkg/net/url/url_test.go
src/pkg/websocket/hixie.go
src/pkg/websocket/hybi.go

index 615d366aedc3d8c71d2aebca47e32abf2998e7e1..73a9b6ea6811c105a2cc2430fc5f89ef91db97d4 100644 (file)
@@ -124,7 +124,7 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
                "GATEWAY_INTERFACE=CGI/1.1",
                "REQUEST_METHOD=" + req.Method,
                "QUERY_STRING=" + req.URL.RawQuery,
-               "REQUEST_URI=" + req.URL.RawPath,
+               "REQUEST_URI=" + req.URL.RequestURI(),
                "PATH_INFO=" + pathInfo,
                "SCRIPT_NAME=" + root,
                "SCRIPT_FILENAME=" + h.Path,
index 1f16fcf86ddb48a2249f73dba7eb3a04c56716fe..3d36f30e32f3f36d2f150852957c66daa11f1745 100644 (file)
@@ -121,9 +121,8 @@ func send(req *Request, t RoundTripper) (resp *Response, err error) {
                req.Header = make(Header)
        }
 
-       info := req.URL.RawUserinfo
-       if len(info) > 0 {
-               req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(info)))
+       if u := req.URL.User; u != nil {
+               req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String())))
        }
        return t.RoundTrip(req)
 }
index 31696aec86ed7b903c2a48f82a8514b1833608d9..61b18ffb1c6d94d4a88583b7d9e695ddd4598bab 100644 (file)
@@ -124,16 +124,8 @@ func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
 
        var b bytes.Buffer
 
-       urlStr := req.URL.Raw
-       if urlStr == "" {
-               urlStr = valueOrDefault(req.URL.EncodedPath(), "/")
-               if req.URL.RawQuery != "" {
-                       urlStr += "?" + req.URL.RawQuery
-               }
-       }
-
-       fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"), urlStr,
-               req.ProtoMajor, req.ProtoMinor)
+       fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
+               req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor)
 
        host := req.Host
        if host == "" && req.URL != nil {
index 1dc83e7d032610ec2b9f160ccf64afa7ba53eff5..1072e2e342604bb47fa32812f45569f73821cab1 100644 (file)
@@ -59,11 +59,6 @@ func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
                req.URL.Scheme = target.Scheme
                req.URL.Host = target.Host
                req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
-               if q := req.URL.RawQuery; q != "" {
-                       req.URL.RawPath = req.URL.Path + "?" + q
-               } else {
-                       req.URL.RawPath = req.URL.Path
-               }
                req.URL.RawQuery = target.RawQuery
        }
        return &ReverseProxy{Director: director}
index ad7e3c02b0ca0f382e3746319cefdcb725d23407..da3e4050fe11368b830df4b70bc62506507e2259 100644 (file)
@@ -44,15 +44,9 @@ var reqTests = []reqTest{
                &Request{
                        Method: "GET",
                        URL: &url.URL{
-                               Raw:          "http://www.techcrunch.com/",
-                               Scheme:       "http",
-                               RawPath:      "/",
-                               RawAuthority: "www.techcrunch.com",
-                               RawUserinfo:  "",
-                               Host:         "www.techcrunch.com",
-                               Path:         "/",
-                               RawQuery:     "",
-                               Fragment:     "",
+                               Scheme: "http",
+                               Host:   "www.techcrunch.com",
+                               Path:   "/",
                        },
                        Proto:      "HTTP/1.1",
                        ProtoMajor: 1,
@@ -86,9 +80,7 @@ var reqTests = []reqTest{
                &Request{
                        Method: "GET",
                        URL: &url.URL{
-                               Raw:     "/",
-                               Path:    "/",
-                               RawPath: "/",
+                               Path: "/",
                        },
                        Proto:         "HTTP/1.1",
                        ProtoMajor:    1,
@@ -113,15 +105,7 @@ var reqTests = []reqTest{
                &Request{
                        Method: "GET",
                        URL: &url.URL{
-                               Raw:          "//user@host/is/actually/a/path/",
-                               Scheme:       "",
-                               RawPath:      "//user@host/is/actually/a/path/",
-                               RawAuthority: "",
-                               RawUserinfo:  "",
-                               Host:         "",
-                               Path:         "//user@host/is/actually/a/path/",
-                               RawQuery:     "",
-                               Fragment:     "",
+                               Path: "//user@host/is/actually/a/path/",
                        },
                        Proto:         "HTTP/1.1",
                        ProtoMajor:    1,
@@ -170,9 +154,7 @@ var reqTests = []reqTest{
                &Request{
                        Method: "POST",
                        URL: &url.URL{
-                               Raw:     "/",
-                               Path:    "/",
-                               RawPath: "/",
+                               Path: "/",
                        },
                        TransferEncoding: []string{"chunked"},
                        Proto:            "HTTP/1.1",
index 260301005ebb13374b7ef082abe4f0aa81308855..5a4e739073ac9278ef481c11d77bc6a37ddad9a6 100644 (file)
@@ -302,26 +302,14 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) err
                host = req.URL.Host
        }
 
-       urlStr := req.URL.RawPath
-       if strings.HasPrefix(urlStr, "?") {
-               urlStr = "/" + urlStr // Issue 2344
-       }
-       if urlStr == "" {
-               urlStr = valueOrDefault(req.URL.RawPath, valueOrDefault(req.URL.EncodedPath(), "/"))
-               if req.URL.RawQuery != "" {
-                       urlStr += "?" + req.URL.RawQuery
-               }
-               if usingProxy {
-                       if urlStr == "" || urlStr[0] != '/' {
-                               urlStr = "/" + urlStr
-                       }
-                       urlStr = req.URL.Scheme + "://" + host + urlStr
-               }
+       ruri := req.URL.RequestURI()
+       if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" {
+               ruri = req.URL.Scheme + "://" + host + ruri
        }
-       // TODO(bradfitz): escape at least newlines in urlStr?
+       // TODO(bradfitz): escape at least newlines in ruri?
 
        bw := bufio.NewWriter(w)
-       fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), urlStr)
+       fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri)
 
        // Header lines
        fmt.Fprintf(bw, "Host: %s\r\n", host)
index 8081589f5f25d8fc505b6ab1bf2ad0abe0f9ca66..fc3186f0c0cb49af50201385a17daefa3dd458a7 100644 (file)
@@ -32,15 +32,9 @@ var reqWriteTests = []reqWriteTest{
                Req: Request{
                        Method: "GET",
                        URL: &url.URL{
-                               Raw:          "http://www.techcrunch.com/",
-                               Scheme:       "http",
-                               RawPath:      "http://www.techcrunch.com/",
-                               RawAuthority: "www.techcrunch.com",
-                               RawUserinfo:  "",
-                               Host:         "www.techcrunch.com",
-                               Path:         "/",
-                               RawQuery:     "",
-                               Fragment:     "",
+                               Scheme: "http",
+                               Host:   "www.techcrunch.com",
+                               Path:   "/",
                        },
                        Proto:      "HTTP/1.1",
                        ProtoMajor: 1,
@@ -60,7 +54,7 @@ var reqWriteTests = []reqWriteTest{
                        Form:  map[string][]string{},
                },
 
-               WantWrite: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
+               WantWrite: "GET / HTTP/1.1\r\n" +
                        "Host: www.techcrunch.com\r\n" +
                        "User-Agent: Fake\r\n" +
                        "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
@@ -198,7 +192,7 @@ var reqWriteTests = []reqWriteTest{
                        "\r\n" +
                        "abcdef",
 
-               WantProxy: "POST / HTTP/1.1\r\n" +
+               WantProxy: "POST http://example.com/ HTTP/1.1\r\n" +
                        "Host: example.com\r\n" +
                        "User-Agent: Go http package\r\n" +
                        "Content-Length: 6\r\n" +
index 24e6b50dab87e6d74d128927d8c40a72f9a02587..265cb2761a3f5e09ce34ee3dbe9ff977350440d1 100644 (file)
@@ -642,7 +642,7 @@ func TestServerExpect(t *testing.T) {
                // Note using r.FormValue("readbody") because for POST
                // requests that would read from r.Body, which we only
                // conditionally want to do.
-               if strings.Contains(r.URL.RawPath, "readbody=true") {
+               if strings.Contains(r.URL.RawQuery, "readbody=true") {
                        ioutil.ReadAll(r.Body)
                        w.Write([]byte("Hi"))
                } else {
index 33ad32861b5b3fc382cf9980f4b13dd47e708f17..1b9ad1b85c55178264df1aa1bbb78739a7826f59 100644 (file)
@@ -229,9 +229,8 @@ func (cm *connectMethod) proxyAuth() string {
        if cm.proxyURL == nil {
                return ""
        }
-       proxyInfo := cm.proxyURL.RawUserinfo
-       if proxyInfo != "" {
-               return "Basic " + base64.URLEncoding.EncodeToString([]byte(proxyInfo))
+       if u := cm.proxyURL.User; u != nil {
+               return "Basic " + base64.URLEncoding.EncodeToString([]byte(u.String()))
        }
        return ""
 }
@@ -332,7 +331,7 @@ func (t *Transport) getConn(cm *connectMethod) (*persistConn, error) {
        case cm.targetScheme == "https":
                connectReq := &Request{
                        Method: "CONNECT",
-                       URL:    &url.URL{RawPath: cm.targetAddr},
+                       URL:    &url.URL{Opaque: cm.targetAddr},
                        Host:   cm.targetAddr,
                        Header: make(Header),
                }
index 11fa18961a19b3e858de41bed6760b367b1d3b7a..0068e98affa5fee73b615badc4d5cd147eefa156 100644 (file)
@@ -52,7 +52,6 @@ const (
        encodeUserPassword
        encodeQueryComponent
        encodeFragment
-       encodeOpaque
 )
 
 type EscapeError string
@@ -69,6 +68,7 @@ func shouldEscape(c byte, mode encoding) bool {
        if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
                return false
        }
+       // TODO: Update the character sets after RFC 3986.
        switch c {
        case '-', '_', '.', '!', '~', '*', '\'', '(', ')': // Â§2.3 Unreserved characters (mark)
                return false
@@ -78,12 +78,10 @@ func shouldEscape(c byte, mode encoding) bool {
                // the reserved characters to appear unescaped.
                switch mode {
                case encodePath: // Â§3.3
-                       // The RFC allows : @ & = + $ , but saves / ; for assigning
-                       // meaning to individual path segments.  This package
+                       // The RFC allows : @ & = + $ but saves / ; , for assigning
+                       // meaning to individual path segments. This package
                        // only manipulates the path as a whole, so we allow those
-                       // last two as well.  Clients that need to distinguish between
-                       // `/foo;y=z/bar` and `/foo%3by=z/bar` will have to re-decode RawPath.
-                       // That leaves only ? to escape.
+                       // last two as well. That leaves only ? to escape.
                        return c == '?'
 
                case encodeUserPassword: // Â§3.2.2
@@ -99,12 +97,6 @@ func shouldEscape(c byte, mode encoding) bool {
                        // The RFC text is silent but the grammar allows
                        // everything, so escape nothing.
                        return false
-
-               case encodeOpaque: // Â§3 opaque_part
-                       // The RFC allows opaque_part to use all characters
-                       // except that the leading / must be escaped.
-                       // (We implement that case in String.)
-                       return false
                }
        }
 
@@ -217,64 +209,73 @@ func escape(s string, mode encoding) string {
        return string(t)
 }
 
-// UnescapeUserinfo parses the RawUserinfo field of a URL
-// as the form user or user:password and unescapes and returns
-// the two halves.
+// A URL represents a parsed URL (technically, a URI reference).
+// The general form represented is:
 //
-// This functionality should only be used with legacy web sites.
-// RFC 2396 warns that interpreting Userinfo this way
-// ``is NOT RECOMMENDED, because the passing of authentication
-// information in clear text (such as URI) has proven to be a
-// security risk in almost every case where it has been used.''
-func UnescapeUserinfo(rawUserinfo string) (user, password string, err error) {
-       u, p := split(rawUserinfo, ':', true)
-       if user, err = unescape(u, encodeUserPassword); err != nil {
-               return "", "", err
-       }
-       if password, err = unescape(p, encodeUserPassword); err != nil {
-               return "", "", err
-       }
-       return
+//     scheme://[userinfo@]host/path[?query][#fragment]
+//
+// URLs that do not start with a slash after the scheme are interpreted as:
+//
+//     scheme:opaque[?query][#fragment]
+//
+type URL struct {
+       Scheme   string
+       Opaque   string    // encoded opaque data
+       User     *Userinfo // username and password information
+       Host     string
+       Path     string
+       RawQuery string // encoded query values, without '?'
+       Fragment string // fragment for references, without '#'
 }
 
-// EscapeUserinfo combines user and password in the form
-// user:password (or just user if password is empty) and then
-// escapes it for use as the URL.RawUserinfo field.
-//
+// User returns a Userinfo containing the provided username
+// and no password set.
+func User(username string) *Userinfo {
+       return &Userinfo{username, "", false}
+}
+
+// UserPassword returns a Userinfo containing the provided username
+// and password.
 // This functionality should only be used with legacy web sites.
 // RFC 2396 warns that interpreting Userinfo this way
 // ``is NOT RECOMMENDED, because the passing of authentication
 // information in clear text (such as URI) has proven to be a
 // security risk in almost every case where it has been used.''
-func EscapeUserinfo(user, password string) string {
-       raw := escape(user, encodeUserPassword)
-       if password != "" {
-               raw += ":" + escape(password, encodeUserPassword)
+func UserPassword(username, password string) *Userinfo {
+       return &Userinfo{username, password, true}
+}
+
+// The Userinfo type is an immutable encapsulation of username and
+// password details for a URL. An existing Userinfo value is guaranteed
+// to have a username set (potentially empty, as allowed by RFC 2396),
+// and optionally a password.
+type Userinfo struct {
+       username    string
+       password    string
+       passwordSet bool
+}
+
+// Username returns the username.
+func (u *Userinfo) Username() string {
+       return u.username
+}
+
+// Password returns the password in case it is set, and whether it is set.
+func (u *Userinfo) Password() (string, bool) {
+       if u.passwordSet {
+               return u.password, true
        }
-       return raw
+       return "", false
 }
 
-// A URL represents a parsed URL (technically, a URI reference).
-// The general form represented is:
-//     scheme://[userinfo@]host/path[?query][#fragment]
-// The Raw, RawAuthority, RawPath, and RawQuery fields are in "wire format"
-// (special characters must be hex-escaped if not meant to have special meaning).
-// All other fields are logical values; '+' or '%' represent themselves.
-//
-// The various Raw values are supplied in wire format because
-// clients typically have to split them into pieces before further
-// decoding.
-type URL struct {
-       Raw          string // the original string
-       Scheme       string // scheme
-       RawAuthority string // [userinfo@]host
-       RawUserinfo  string // userinfo
-       Host         string // host
-       RawPath      string // /path[?query][#fragment]
-       Path         string // /path
-       OpaquePath   bool   // path is opaque (unrooted when scheme is present)
-       RawQuery     string // query
-       Fragment     string // fragment
+// String returns the encoded userinfo information in the standard form
+// of "username[:password]".
+func (u *Userinfo) String() string {
+       s := escape(u.username, encodeUserPassword)
+       if u.passwordSet {
+               s += ":" + escape(u.password, encodeUserPassword)
+       }
+       return s
 }
 
 // Maybe rawurl is of the form scheme:path.
@@ -341,136 +342,112 @@ func ParseRequest(rawurl string) (url *URL, err error) {
 // in which case only absolute URLs or path-absolute relative URLs are allowed.
 // If viaRequest is false, all forms of relative URLs are allowed.
 func parse(rawurl string, viaRequest bool) (url *URL, err error) {
-       var (
-               leadingSlash bool
-               path         string
-       )
+       var rest string
 
        if rawurl == "" {
                err = errors.New("empty url")
                goto Error
        }
        url = new(URL)
-       url.Raw = rawurl
 
        // Split off possible leading "http:", "mailto:", etc.
        // Cannot contain escaped characters.
-       if url.Scheme, path, err = getscheme(rawurl); err != nil {
+       if url.Scheme, rest, err = getscheme(rawurl); err != nil {
                goto Error
        }
-       leadingSlash = strings.HasPrefix(path, "/")
 
-       if url.Scheme != "" && !leadingSlash {
-               // RFC 2396:
-               // Absolute URI (has scheme) with non-rooted path
-               // is uninterpreted.  It doesn't even have a ?query.
-               // This is the case that handles mailto:name@example.com.
-               url.RawPath = path
+       rest, url.RawQuery = split(rest, '?', true)
 
-               if url.Path, err = unescape(path, encodeOpaque); err != nil {
-                       goto Error
+       if !strings.HasPrefix(rest, "/") {
+               if url.Scheme != "" {
+                       // We consider rootless paths per RFC 3986 as opaque.
+                       url.Opaque = rest
+                       return url, nil
                }
-               url.OpaquePath = true
-       } else {
-               if viaRequest && !leadingSlash {
+               if viaRequest {
                        err = errors.New("invalid URI for request")
                        goto Error
                }
+       }
 
-               // Split off query before parsing path further.
-               url.RawPath = path
-               path, query := split(path, '?', false)
-               if len(query) > 1 {
-                       url.RawQuery = query[1:]
-               }
-
-               // Maybe path is //authority/path
-               if (url.Scheme != "" || !viaRequest) &&
-                       strings.HasPrefix(path, "//") && !strings.HasPrefix(path, "///") {
-                       url.RawAuthority, path = split(path[2:], '/', false)
-                       url.RawPath = url.RawPath[2+len(url.RawAuthority):]
-               }
-
-               // Split authority into userinfo@host.
-               // If there's no @, split's default is wrong.  Check explicitly.
-               var rawHost string
-               if strings.Index(url.RawAuthority, "@") < 0 {
-                       rawHost = url.RawAuthority
-               } else {
-                       url.RawUserinfo, rawHost = split(url.RawAuthority, '@', true)
-               }
-
-               // We leave RawAuthority only in raw form because clients
-               // of common protocols should be using Userinfo and Host
-               // instead.  Clients that wish to use RawAuthority will have to
-               // interpret it themselves: RFC 2396 does not define the meaning.
-
-               if strings.Contains(rawHost, "%") {
-                       // Host cannot contain escaped characters.
-                       err = errors.New("hexadecimal escape in host")
+       if (url.Scheme != "" || !viaRequest) && strings.HasPrefix(rest, "//") && !strings.HasPrefix(rest, "///") {
+               var authority string
+               authority, rest = split(rest[2:], '/', false)
+               url.User, url.Host, err = parseAuthority(authority)
+               if err != nil {
                        goto Error
                }
-               url.Host = rawHost
-
-               if url.Path, err = unescape(path, encodePath); err != nil {
+               if strings.Contains(url.Host, "%") {
+                       err = errors.New("hexadecimal escape in host")
                        goto Error
                }
        }
+       if url.Path, err = unescape(rest, encodePath); err != nil {
+               goto Error
+       }
        return url, nil
 
 Error:
        return nil, &Error{"parse", rawurl, err}
+}
 
+func parseAuthority(authority string) (user *Userinfo, host string, err error) {
+       if strings.Index(authority, "@") < 0 {
+               host = authority
+               return
+       }
+       userinfo, host := split(authority, '@', true)
+       if strings.Index(userinfo, ":") < 0 {
+               if userinfo, err = unescape(userinfo, encodeUserPassword); err != nil {
+                       return
+               }
+               user = User(userinfo)
+       } else {
+               username, password := split(userinfo, ':', true)
+               if username, err = unescape(username, encodeUserPassword); err != nil {
+                       return
+               }
+               if password, err = unescape(password, encodeUserPassword); err != nil {
+                       return
+               }
+               user = UserPassword(username, password)
+       }
+       return
 }
 
 // ParseWithReference is like Parse but allows a trailing #fragment.
 func ParseWithReference(rawurlref string) (url *URL, err error) {
-       // Cut off #frag.
-       rawurl, frag := split(rawurlref, '#', false)
+       // Cut off #frag
+       rawurl, frag := split(rawurlref, '#', true)
        if url, err = Parse(rawurl); err != nil {
                return nil, err
        }
-       url.Raw += frag
-       url.RawPath += frag
-       if len(frag) > 1 {
-               frag = frag[1:]
-               if url.Fragment, err = unescape(frag, encodeFragment); err != nil {
-                       return nil, &Error{"parse", rawurl, err}
-               }
+       if frag == "" {
+               return url, nil
+       }
+       if url.Fragment, err = unescape(frag, encodeFragment); err != nil {
+               return nil, &Error{"parse", rawurlref, err}
        }
        return url, nil
 }
 
 // String reassembles url into a valid URL string.
-//
-// There are redundant fields stored in the URL structure:
-// the String method consults Scheme, Path, Host, RawUserinfo,
-// RawQuery, and Fragment, but not Raw, RawPath or RawAuthority.
 func (url *URL) String() string {
+       // TODO: Rewrite to use bytes.Buffer
        result := ""
        if url.Scheme != "" {
                result += url.Scheme + ":"
        }
-       if url.Host != "" || url.RawUserinfo != "" {
-               result += "//"
-               if url.RawUserinfo != "" {
-                       // hide the password, if any
-                       info := url.RawUserinfo
-                       if i := strings.Index(info, ":"); i >= 0 {
-                               info = info[0:i] + ":******"
+       if url.Opaque != "" {
+               result += url.Opaque
+       } else {
+               if url.Host != "" || url.User != nil {
+                       result += "//"
+                       if u := url.User; u != nil {
+                               result += u.String() + "@"
                        }
-                       result += info + "@"
-               }
-               result += url.Host
-       }
-       if url.OpaquePath {
-               path := url.Path
-               if strings.HasPrefix(path, "/") {
-                       result += "%2f"
-                       path = path[1:]
+                       result += url.Host
                }
-               result += escape(path, encodeOpaque)
-       } else {
                result += escape(url.Path, encodePath)
        }
        if url.RawQuery != "" {
@@ -630,47 +607,38 @@ func (base *URL) Parse(ref string) (*URL, error) {
 // 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
-                       }
+       if ref.IsAbs() {
+               url := *ref
+               return &url
+       }
+       // relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
+       url := *base
+       url.RawQuery = ref.RawQuery
+       url.Fragment = ref.Fragment
+       if ref.Opaque != "" {
+               url.Opaque = ref.Opaque
+               url.User = nil
+               url.Host = ""
+               url.Path = ""
+               return &url
+       }
+       if ref.Host != "" || ref.User != nil {
+               // The "net_path" case.
+               url.Host = ref.Host
+               url.User = ref.User
+       }
+       if strings.HasPrefix(ref.Path, "/") {
+               // The "abs_path" case.
+               url.Path = ref.Path
+       } else {
+               // The "rel_path" case.
+               path := resolvePath(base.Path, ref.Path)
+               if !strings.HasPrefix(path, "/") {
+                       path = "/" + path
                }
-
-               url.Fragment = ref.Fragment
+               url.Path = path
        }
-       url.Raw = url.String()
-       return url
+       return &url
 }
 
 // Query parses RawQuery and returns the corresponding values.
@@ -679,7 +647,18 @@ func (u *URL) Query() Values {
        return v
 }
 
-// EncodedPath returns the URL's path in "URL path encoded" form.
-func (u *URL) EncodedPath() string {
-       return escape(u.Path, encodePath)
+// RequestURI returns the encoded path?query or opaque?query
+// string that would be used in an HTTP request for u.
+func (u *URL) RequestURI() string {
+       result := u.Opaque
+       if result == "" {
+               result = escape(u.Path, encodePath)
+               if result == "" {
+                       result = "/"
+               }
+       }
+       if u.RawQuery != "" {
+               result += "?" + u.RawQuery
+       }
+       return result
 }
index dab3bfa1bbd5110df087782965e10fbea32c2a5b..9fe5ff886b7d8be48ef6e366e8a4347be9daf9cb 100644 (file)
@@ -21,10 +21,8 @@ var urltests = []URLTest{
        {
                "http://www.google.com",
                &URL{
-                       Raw:          "http://www.google.com",
-                       Scheme:       "http",
-                       RawAuthority: "www.google.com",
-                       Host:         "www.google.com",
+                       Scheme: "http",
+                       Host:   "www.google.com",
                },
                "",
        },
@@ -32,12 +30,9 @@ var urltests = []URLTest{
        {
                "http://www.google.com/",
                &URL{
-                       Raw:          "http://www.google.com/",
-                       Scheme:       "http",
-                       RawAuthority: "www.google.com",
-                       Host:         "www.google.com",
-                       RawPath:      "/",
-                       Path:         "/",
+                       Scheme: "http",
+                       Host:   "www.google.com",
+                       Path:   "/",
                },
                "",
        },
@@ -45,12 +40,9 @@ var urltests = []URLTest{
        {
                "http://www.google.com/file%20one%26two",
                &URL{
-                       Raw:          "http://www.google.com/file%20one%26two",
-                       Scheme:       "http",
-                       RawAuthority: "www.google.com",
-                       Host:         "www.google.com",
-                       RawPath:      "/file%20one%26two",
-                       Path:         "/file one&two",
+                       Scheme: "http",
+                       Host:   "www.google.com",
+                       Path:   "/file one&two",
                },
                "http://www.google.com/file%20one&two",
        },
@@ -58,13 +50,10 @@ var urltests = []URLTest{
        {
                "ftp://webmaster@www.google.com/",
                &URL{
-                       Raw:          "ftp://webmaster@www.google.com/",
-                       Scheme:       "ftp",
-                       RawAuthority: "webmaster@www.google.com",
-                       RawUserinfo:  "webmaster",
-                       Host:         "www.google.com",
-                       RawPath:      "/",
-                       Path:         "/",
+                       Scheme: "ftp",
+                       User:   User("webmaster"),
+                       Host:   "www.google.com",
+                       Path:   "/",
                },
                "",
        },
@@ -72,13 +61,10 @@ var urltests = []URLTest{
        {
                "ftp://john%20doe@www.google.com/",
                &URL{
-                       Raw:          "ftp://john%20doe@www.google.com/",
-                       Scheme:       "ftp",
-                       RawAuthority: "john%20doe@www.google.com",
-                       RawUserinfo:  "john%20doe",
-                       Host:         "www.google.com",
-                       RawPath:      "/",
-                       Path:         "/",
+                       Scheme: "ftp",
+                       User:   User("john doe"),
+                       Host:   "www.google.com",
+                       Path:   "/",
                },
                "ftp://john%20doe@www.google.com/",
        },
@@ -86,13 +72,10 @@ var urltests = []URLTest{
        {
                "http://www.google.com/?q=go+language",
                &URL{
-                       Raw:          "http://www.google.com/?q=go+language",
-                       Scheme:       "http",
-                       RawAuthority: "www.google.com",
-                       Host:         "www.google.com",
-                       RawPath:      "/?q=go+language",
-                       Path:         "/",
-                       RawQuery:     "q=go+language",
+                       Scheme:   "http",
+                       Host:     "www.google.com",
+                       Path:     "/",
+                       RawQuery: "q=go+language",
                },
                "",
        },
@@ -100,13 +83,10 @@ var urltests = []URLTest{
        {
                "http://www.google.com/?q=go%20language",
                &URL{
-                       Raw:          "http://www.google.com/?q=go%20language",
-                       Scheme:       "http",
-                       RawAuthority: "www.google.com",
-                       Host:         "www.google.com",
-                       RawPath:      "/?q=go%20language",
-                       Path:         "/",
-                       RawQuery:     "q=go%20language",
+                       Scheme:   "http",
+                       Host:     "www.google.com",
+                       Path:     "/",
+                       RawQuery: "q=go%20language",
                },
                "",
        },
@@ -114,48 +94,39 @@ var urltests = []URLTest{
        {
                "http://www.google.com/a%20b?q=c+d",
                &URL{
-                       Raw:          "http://www.google.com/a%20b?q=c+d",
-                       Scheme:       "http",
-                       RawAuthority: "www.google.com",
-                       Host:         "www.google.com",
-                       RawPath:      "/a%20b?q=c+d",
-                       Path:         "/a b",
-                       RawQuery:     "q=c+d",
+                       Scheme:   "http",
+                       Host:     "www.google.com",
+                       Path:     "/a b",
+                       RawQuery: "q=c+d",
                },
                "",
        },
-       // path without leading /, so no query parsing
+       // path without leading /, so no parsing
        {
                "http:www.google.com/?q=go+language",
                &URL{
-                       Raw:        "http:www.google.com/?q=go+language",
-                       Scheme:     "http",
-                       RawPath:    "www.google.com/?q=go+language",
-                       Path:       "www.google.com/?q=go+language",
-                       OpaquePath: true,
+                       Scheme:   "http",
+                       Opaque:   "www.google.com/",
+                       RawQuery: "q=go+language",
                },
                "http:www.google.com/?q=go+language",
        },
-       // path without leading /, so no query parsing
+       // path without leading /, so no parsing
        {
                "http:%2f%2fwww.google.com/?q=go+language",
                &URL{
-                       Raw:        "http:%2f%2fwww.google.com/?q=go+language",
-                       Scheme:     "http",
-                       RawPath:    "%2f%2fwww.google.com/?q=go+language",
-                       Path:       "//www.google.com/?q=go+language",
-                       OpaquePath: true,
+                       Scheme:   "http",
+                       Opaque:   "%2f%2fwww.google.com/",
+                       RawQuery: "q=go+language",
                },
-               "http:%2f/www.google.com/?q=go+language",
+               "http:%2f%2fwww.google.com/?q=go+language",
        },
        // non-authority
        {
                "mailto:/webmaster@golang.org",
                &URL{
-                       Raw:     "mailto:/webmaster@golang.org",
-                       Scheme:  "mailto",
-                       RawPath: "/webmaster@golang.org",
-                       Path:    "/webmaster@golang.org",
+                       Scheme: "mailto",
+                       Path:   "/webmaster@golang.org",
                },
                "",
        },
@@ -163,11 +134,8 @@ var urltests = []URLTest{
        {
                "mailto:webmaster@golang.org",
                &URL{
-                       Raw:        "mailto:webmaster@golang.org",
-                       Scheme:     "mailto",
-                       RawPath:    "webmaster@golang.org",
-                       Path:       "webmaster@golang.org",
-                       OpaquePath: true,
+                       Scheme: "mailto",
+                       Opaque: "webmaster@golang.org",
                },
                "",
        },
@@ -175,8 +143,6 @@ var urltests = []URLTest{
        {
                "/foo?query=http://bad",
                &URL{
-                       Raw:      "/foo?query=http://bad",
-                       RawPath:  "/foo?query=http://bad",
                        Path:     "/foo",
                        RawQuery: "query=http://bad",
                },
@@ -186,12 +152,7 @@ var urltests = []URLTest{
        {
                "//foo",
                &URL{
-                       RawAuthority: "foo",
-                       Raw:          "//foo",
-                       Host:         "foo",
-                       Scheme:       "",
-                       RawPath:      "",
-                       Path:         "",
+                       Host: "foo",
                },
                "",
        },
@@ -199,14 +160,10 @@ var urltests = []URLTest{
        {
                "//user@foo/path?a=b",
                &URL{
-                       Raw:          "//user@foo/path?a=b",
-                       RawAuthority: "user@foo",
-                       RawUserinfo:  "user",
-                       Scheme:       "",
-                       RawPath:      "/path?a=b",
-                       Path:         "/path",
-                       RawQuery:     "a=b",
-                       Host:         "foo",
+                       User:     User("user"),
+                       Host:     "foo",
+                       Path:     "/path",
+                       RawQuery: "a=b",
                },
                "",
        },
@@ -218,36 +175,18 @@ var urltests = []URLTest{
        {
                "///threeslashes",
                &URL{
-                       RawAuthority: "",
-                       Raw:          "///threeslashes",
-                       Host:         "",
-                       Scheme:       "",
-                       RawPath:      "///threeslashes",
-                       Path:         "///threeslashes",
+                       Path: "///threeslashes",
                },
                "",
        },
        {
                "http://user:password@google.com",
                &URL{
-                       Raw:          "http://user:password@google.com",
-                       Scheme:       "http",
-                       RawAuthority: "user:password@google.com",
-                       RawUserinfo:  "user:password",
-                       Host:         "google.com",
-               },
-               "http://user:******@google.com",
-       },
-       {
-               "http://user:longerpass@google.com",
-               &URL{
-                       Raw:          "http://user:longerpass@google.com",
-                       Scheme:       "http",
-                       RawAuthority: "user:longerpass@google.com",
-                       RawUserinfo:  "user:longerpass",
-                       Host:         "google.com",
+                       Scheme: "http",
+                       User:   UserPassword("user", "password"),
+                       Host:   "google.com",
                },
-               "http://user:******@google.com",
+               "http://user:password@google.com",
        },
 }
 
@@ -255,13 +194,10 @@ var urlnofragtests = []URLTest{
        {
                "http://www.google.com/?q=go+language#foo",
                &URL{
-                       Raw:          "http://www.google.com/?q=go+language#foo",
-                       Scheme:       "http",
-                       RawAuthority: "www.google.com",
-                       Host:         "www.google.com",
-                       RawPath:      "/?q=go+language#foo",
-                       Path:         "/",
-                       RawQuery:     "q=go+language#foo",
+                       Scheme:   "http",
+                       Host:     "www.google.com",
+                       Path:     "/",
+                       RawQuery: "q=go+language#foo",
                },
                "",
        },
@@ -271,28 +207,22 @@ var urlfragtests = []URLTest{
        {
                "http://www.google.com/?q=go+language#foo",
                &URL{
-                       Raw:          "http://www.google.com/?q=go+language#foo",
-                       Scheme:       "http",
-                       RawAuthority: "www.google.com",
-                       Host:         "www.google.com",
-                       RawPath:      "/?q=go+language#foo",
-                       Path:         "/",
-                       RawQuery:     "q=go+language",
-                       Fragment:     "foo",
+                       Scheme:   "http",
+                       Host:     "www.google.com",
+                       Path:     "/",
+                       RawQuery: "q=go+language",
+                       Fragment: "foo",
                },
                "",
        },
        {
                "http://www.google.com/?q=go+language#foo%26bar",
                &URL{
-                       Raw:          "http://www.google.com/?q=go+language#foo%26bar",
-                       Scheme:       "http",
-                       RawAuthority: "www.google.com",
-                       Host:         "www.google.com",
-                       RawPath:      "/?q=go+language#foo%26bar",
-                       Path:         "/",
-                       RawQuery:     "q=go+language",
-                       Fragment:     "foo&bar",
+                       Scheme:   "http",
+                       Host:     "www.google.com",
+                       Path:     "/",
+                       RawQuery: "q=go+language",
+                       Fragment: "foo&bar",
                },
                "http://www.google.com/?q=go+language#foo&bar",
        },
@@ -300,9 +230,15 @@ var urlfragtests = []URLTest{
 
 // more useful string for debugging than fmt's struct printer
 func ufmt(u *URL) string {
-       return fmt.Sprintf("raw=%q, scheme=%q, rawpath=%q, auth=%q, userinfo=%q, host=%q, path=%q, rawq=%q, frag=%q",
-               u.Raw, u.Scheme, u.RawPath, u.RawAuthority, u.RawUserinfo,
-               u.Host, u.Path, u.RawQuery, u.Fragment)
+       var user, pass interface{}
+       if u.User != nil {
+               user = u.User.Username()
+               if p, ok := u.User.Password(); ok {
+                       pass = p
+               }
+       }
+       return fmt.Sprintf("opaque=%q, scheme=%q, user=%#v, pass=%#v, host=%q, path=%q, rawq=%q, frag=%q",
+               u.Opaque, u.Scheme, user, pass, u.Host, u.Path, u.RawQuery, u.Fragment)
 }
 
 func DoTest(t *testing.T, parse func(string) (*URL, error), name string, tests []URLTest) {
@@ -370,11 +306,11 @@ func DoTestString(t *testing.T, parse func(string) (*URL, error), name string, t
                        t.Errorf("%s(%q) returned error %s", name, tt.in, err)
                        continue
                }
-               s := u.String()
                expected := tt.in
                if len(tt.roundtrip) > 0 {
                        expected = tt.roundtrip
                }
+               s := u.String()
                if s != expected {
                        t.Errorf("%s(%q).String() == %q (expected %q)", name, tt.in, s, expected)
                }
@@ -504,33 +440,11 @@ func TestEscape(t *testing.T) {
        }
 }
 
-type UserinfoTest struct {
-       User     string
-       Password string
-       Raw      string
-}
-
-var userinfoTests = []UserinfoTest{
-       {"user", "password", "user:password"},
-       {"foo:bar", "~!@#$%^&*()_+{}|[]\\-=`:;'\"<>?,./",
-               "foo%3Abar:~!%40%23$%25%5E&*()_+%7B%7D%7C%5B%5D%5C-=%60%3A;'%22%3C%3E?,.%2F"},
-}
-
-func TestEscapeUserinfo(t *testing.T) {
-       for _, tt := range userinfoTests {
-               if raw := EscapeUserinfo(tt.User, tt.Password); raw != tt.Raw {
-                       t.Errorf("EscapeUserinfo(%q, %q) = %q, want %q", tt.User, tt.Password, raw, tt.Raw)
-               }
-       }
-}
-
-func TestUnescapeUserinfo(t *testing.T) {
-       for _, tt := range userinfoTests {
-               if user, pass, err := UnescapeUserinfo(tt.Raw); user != tt.User || pass != tt.Password || err != nil {
-                       t.Errorf("UnescapeUserinfo(%q) = %q, %q, %v, want %q, %q, nil", tt.Raw, user, pass, err, tt.User, tt.Password)
-               }
-       }
-}
+//var userinfoTests = []UserinfoTest{
+//     {"user", "password", "user:password"},
+//     {"foo:bar", "~!@#$%^&*()_+{}|[]\\-=`:;'\"<>?,./",
+//             "foo%3Abar:~!%40%23$%25%5E&*()_+%7B%7D%7C%5B%5D%5C-=%60%3A;'%22%3C%3E?,.%2F"},
+//}
 
 type EncodeQueryTest struct {
        m         Values
@@ -664,6 +578,57 @@ func TestResolveReference(t *testing.T) {
                t.Errorf("Expected an error from Parse wrapper parsing an empty string.")
        }
 
+       // Ensure Opaque resets the URL.
+       base = mustParse("scheme://user@foo.com/bar")
+       abs = base.ResolveReference(&URL{Opaque: "opaque"})
+       want := mustParse("scheme:opaque")
+       if *abs != *want {
+               t.Errorf("ResolveReference failed to resolve opaque URL: want %#v, got %#v", abs, want)
+       }
+}
+
+func TestResolveReferenceOpaque(t *testing.T) {
+       mustParse := func(url string) *URL {
+               u, err := ParseWithReference(url)
+               if err != nil {
+                       t.Fatalf("Expected URL to parse: %q, got error: %v", url, err)
+               }
+               return u
+       }
+       for _, test := range resolveReferenceTests {
+               base := mustParse(test.base)
+               rel := mustParse(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 := mustParse("http://foo.com/")
+       abs := base.ResolveReference(mustParse("."))
+       if base == abs {
+               t.Errorf("Expected no-op reference to return new URL instance.")
+       }
+       barRef := mustParse("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 = mustParse("http://foo.com/path/one/")
+       abs, _ = base.Parse("../two")
+       expected := "http://foo.com/path/two"
+       if abs.String() != expected {
+               t.Errorf("Parse wrapper got %q; expected %q", abs.String(), expected)
+       }
+       _, err := base.Parse("")
+       if err == nil {
+               t.Errorf("Expected an error from Parse wrapper parsing an empty string.")
+       }
+
 }
 
 func TestQueryValues(t *testing.T) {
@@ -747,3 +712,60 @@ func TestParseQuery(t *testing.T) {
                }
        }
 }
+
+type RequestURITest struct {
+       url *URL
+       out string
+}
+
+var requritests = []RequestURITest{
+       {
+               &URL{
+                       Scheme: "http",
+                       Host:   "example.com",
+                       Path:   "",
+               },
+               "/",
+       },
+       {
+               &URL{
+                       Scheme: "http",
+                       Host:   "example.com",
+                       Path:   "/a b",
+               },
+               "/a%20b",
+       },
+       {
+               &URL{
+                       Scheme:   "http",
+                       Host:     "example.com",
+                       Path:     "/a b",
+                       RawQuery: "q=go+language",
+               },
+               "/a%20b?q=go+language",
+       },
+       {
+               &URL{
+                       Scheme: "myschema",
+                       Opaque: "opaque",
+               },
+               "opaque",
+       },
+       {
+               &URL{
+                       Scheme:   "myschema",
+                       Opaque:   "opaque",
+                       RawQuery: "q=go+language",
+               },
+               "opaque?q=go+language",
+       },
+}
+
+func TestRequestURI(t *testing.T) {
+       for _, tt := range requritests {
+               s := tt.url.RequestURI()
+               if s != tt.out {
+                       t.Errorf("%#v.RequestURI() == %q (expected %q)", tt.url, s, tt.out)
+               }
+       }
+}
index d0ddbeeb484dc5d9ca425737b046b6b4db87bebc..22dbc1f8db37f204b16e286e443c010a62224170 100644 (file)
@@ -343,7 +343,7 @@ func hixie76ClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer)
        }
        // 4.1. Opening handshake.
        // Step 5.  send a request line.
-       bw.WriteString("GET " + config.Location.RawPath + " HTTP/1.1\r\n")
+       bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
 
        // Step 6-14. push request headers in fields.
        fields := []string{
@@ -456,7 +456,7 @@ func hixie75ClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer)
        if config.Version != ProtocolVersionHixie75 {
                panic("wrong protocol version.")
        }
-       bw.WriteString("GET " + config.Location.RawPath + " HTTP/1.1\r\n")
+       bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
        bw.WriteString("Upgrade: WebSocket\r\n")
        bw.WriteString("Connection: Upgrade\r\n")
        bw.WriteString("Host: " + config.Location.Host + "\r\n")
@@ -557,7 +557,7 @@ func (c *hixie76ServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Req
        } else {
                scheme = "ws"
        }
-       c.Location, err = url.ParseRequest(scheme + "://" + req.Host + req.URL.RawPath)
+       c.Location, err = url.ParseRequest(scheme + "://" + req.Host + req.URL.RequestURI())
        if err != nil {
                return http.StatusBadRequest, err
        }
@@ -653,7 +653,7 @@ func (c *hixie75ServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Req
        } else {
                scheme = "ws"
        }
-       c.Location, err = url.ParseRequest(scheme + "://" + req.Host + req.URL.RawPath)
+       c.Location, err = url.ParseRequest(scheme + "://" + req.Host + req.URL.RequestURI())
        if err != nil {
                return http.StatusBadRequest, err
        }
index ff386dc7f2105cfbbbee3b1314c3c122fbd5e6c3..6b0c5286f2820a8006a9949fdc34484db76fc22a 100644 (file)
@@ -390,7 +390,7 @@ func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (er
                panic("wrong protocol version.")
        }
 
-       bw.WriteString("GET " + config.Location.RawPath + " HTTP/1.1\r\n")
+       bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
 
        bw.WriteString("Host: " + config.Location.Host + "\r\n")
        bw.WriteString("Upgrade: websocket\r\n")
@@ -505,7 +505,7 @@ func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Reques
        } else {
                scheme = "ws"
        }
-       c.Location, err = url.ParseRequest(scheme + "://" + req.Host + req.URL.RawPath)
+       c.Location, err = url.ParseRequest(scheme + "://" + req.Host + req.URL.RequestURI())
        if err != nil {
                return http.StatusBadRequest, err
        }