Commit dafd9f0b authored by Gustavo Niemeyer's avatar Gustavo Niemeyer

net/url: cleaned up URL interface (v2)

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