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", Host: "www.techcrunch.com",
RawPath: "/", Path: "/",
RawAuthority: "www.techcrunch.com",
RawUserinfo: "",
Host: "www.techcrunch.com",
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/", Path: "//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: "",
}, },
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 == "" {
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? // TODO(bradfitz): escape at least newlines in ruri?
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", Host: "www.techcrunch.com",
RawPath: "http://www.techcrunch.com/", Path: "/",
RawAuthority: "www.techcrunch.com",
RawUserinfo: "",
Host: "www.techcrunch.com",
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),
} }
......
This diff is collapsed.
This diff is collapsed.
...@@ -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