Commit 6b6cb725 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

http: prevent DumpRequest from adding implicit headers

Fixes #2272

R=rsc
CC=golang-dev
https://golang.org/cl/5043051
parent 24257a1e
...@@ -44,7 +44,7 @@ func DumpRequest(req *Request, body bool) (dump []byte, err os.Error) { ...@@ -44,7 +44,7 @@ func DumpRequest(req *Request, body bool) (dump []byte, err os.Error) {
return return
} }
} }
err = req.Write(&b) err = req.dumpWrite(&b)
req.Body = save req.Body = save
if err != nil { if err != nil {
return return
......
...@@ -64,13 +64,20 @@ func (e *badStringError) String() string { return fmt.Sprintf("%s %q", e.what, e ...@@ -64,13 +64,20 @@ func (e *badStringError) String() string { return fmt.Sprintf("%s %q", e.what, e
// Headers that Request.Write handles itself and should be skipped. // Headers that Request.Write handles itself and should be skipped.
var reqWriteExcludeHeader = map[string]bool{ var reqWriteExcludeHeader = map[string]bool{
"Host": true, "Host": true, // not in Header map anyway
"User-Agent": true, "User-Agent": true,
"Content-Length": true, "Content-Length": true,
"Transfer-Encoding": true, "Transfer-Encoding": true,
"Trailer": true, "Trailer": true,
} }
var reqWriteExcludeHeaderDump = map[string]bool{
"Host": true, // not in Header map anyway
"Content-Length": true,
"Transfer-Encoding": true,
"Trailer": true,
}
// A Request represents a parsed HTTP request header. // A Request represents a parsed HTTP request header.
type Request struct { type Request struct {
Method string // GET, POST, PUT, etc. Method string // GET, POST, PUT, etc.
...@@ -283,6 +290,53 @@ func (req *Request) WriteProxy(w io.Writer) os.Error { ...@@ -283,6 +290,53 @@ func (req *Request) WriteProxy(w io.Writer) os.Error {
return req.write(w, true) return req.write(w, true)
} }
func (req *Request) dumpWrite(w io.Writer) os.Error {
urlStr := req.RawURL
if urlStr == "" {
urlStr = valueOrDefault(req.URL.EncodedPath(), "/")
if req.URL.RawQuery != "" {
urlStr += "?" + req.URL.RawQuery
}
}
bw := bufio.NewWriter(w)
fmt.Fprintf(bw, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"), urlStr,
req.ProtoMajor, req.ProtoMinor)
host := req.Host
if host == "" && req.URL != nil {
host = req.URL.Host
}
if host != "" {
fmt.Fprintf(bw, "Host: %s\r\n", host)
}
// Process Body,ContentLength,Close,Trailer
tw, err := newTransferWriter(req)
if err != nil {
return err
}
err = tw.WriteHeader(bw)
if err != nil {
return err
}
err = req.Header.WriteSubset(bw, reqWriteExcludeHeaderDump)
if err != nil {
return err
}
io.WriteString(bw, "\r\n")
// Write body and trailer
err = tw.WriteBody(bw)
if err != nil {
return err
}
bw.Flush()
return nil
}
func (req *Request) write(w io.Writer, usingProxy bool) os.Error { func (req *Request) write(w io.Writer, usingProxy bool) os.Error {
host := req.Host host := req.Host
if host == "" { if host == "" {
......
...@@ -16,17 +16,21 @@ import ( ...@@ -16,17 +16,21 @@ import (
) )
type reqWriteTest struct { type reqWriteTest struct {
Req Request Req Request
Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
Raw string
RawProxy string // Any of these three may be empty to skip that test.
WantError os.Error WantWrite string // Request.Write
WantProxy string // Request.WriteProxy
WantDump string // DumpRequest
WantError os.Error // wanted error from Request.Write
} }
var reqWriteTests = []reqWriteTest{ var reqWriteTests = []reqWriteTest{
// HTTP/1.1 => chunked coding; no body; no trailer // HTTP/1.1 => chunked coding; no body; no trailer
{ {
Request{ Req: Request{
Method: "GET", Method: "GET",
RawURL: "http://www.techcrunch.com/", RawURL: "http://www.techcrunch.com/",
URL: &url.URL{ URL: &url.URL{
...@@ -58,9 +62,7 @@ var reqWriteTests = []reqWriteTest{ ...@@ -58,9 +62,7 @@ var reqWriteTests = []reqWriteTest{
Form: map[string][]string{}, Form: map[string][]string{},
}, },
nil, WantWrite: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
"GET http://www.techcrunch.com/ 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" +
...@@ -70,7 +72,7 @@ var reqWriteTests = []reqWriteTest{ ...@@ -70,7 +72,7 @@ var reqWriteTests = []reqWriteTest{
"Keep-Alive: 300\r\n" + "Keep-Alive: 300\r\n" +
"Proxy-Connection: keep-alive\r\n\r\n", "Proxy-Connection: keep-alive\r\n\r\n",
"GET http://www.techcrunch.com/ HTTP/1.1\r\n" + WantProxy: "GET http://www.techcrunch.com/ 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" +
...@@ -79,12 +81,10 @@ var reqWriteTests = []reqWriteTest{ ...@@ -79,12 +81,10 @@ var reqWriteTests = []reqWriteTest{
"Accept-Language: en-us,en;q=0.5\r\n" + "Accept-Language: en-us,en;q=0.5\r\n" +
"Keep-Alive: 300\r\n" + "Keep-Alive: 300\r\n" +
"Proxy-Connection: keep-alive\r\n\r\n", "Proxy-Connection: keep-alive\r\n\r\n",
nil,
}, },
// HTTP/1.1 => chunked coding; body; empty trailer // HTTP/1.1 => chunked coding; body; empty trailer
{ {
Request{ Req: Request{
Method: "GET", Method: "GET",
URL: &url.URL{ URL: &url.URL{
Scheme: "http", Scheme: "http",
...@@ -97,25 +97,28 @@ var reqWriteTests = []reqWriteTest{ ...@@ -97,25 +97,28 @@ var reqWriteTests = []reqWriteTest{
TransferEncoding: []string{"chunked"}, TransferEncoding: []string{"chunked"},
}, },
[]byte("abcdef"), Body: []byte("abcdef"),
"GET /search HTTP/1.1\r\n" + WantWrite: "GET /search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" + "Host: www.google.com\r\n" +
"User-Agent: Go http package\r\n" + "User-Agent: Go http package\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" + "Transfer-Encoding: chunked\r\n\r\n" +
chunk("abcdef") + chunk(""), chunk("abcdef") + chunk(""),
"GET http://www.google.com/search HTTP/1.1\r\n" + WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" + "Host: www.google.com\r\n" +
"User-Agent: Go http package\r\n" + "User-Agent: Go http package\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" + "Transfer-Encoding: chunked\r\n\r\n" +
chunk("abcdef") + chunk(""), chunk("abcdef") + chunk(""),
nil, WantDump: "GET /search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
chunk("abcdef") + chunk(""),
}, },
// HTTP/1.1 POST => chunked coding; body; empty trailer // HTTP/1.1 POST => chunked coding; body; empty trailer
{ {
Request{ Req: Request{
Method: "POST", Method: "POST",
URL: &url.URL{ URL: &url.URL{
Scheme: "http", Scheme: "http",
...@@ -129,28 +132,26 @@ var reqWriteTests = []reqWriteTest{ ...@@ -129,28 +132,26 @@ var reqWriteTests = []reqWriteTest{
TransferEncoding: []string{"chunked"}, TransferEncoding: []string{"chunked"},
}, },
[]byte("abcdef"), Body: []byte("abcdef"),
"POST /search HTTP/1.1\r\n" + WantWrite: "POST /search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" + "Host: www.google.com\r\n" +
"User-Agent: Go http package\r\n" + "User-Agent: Go http package\r\n" +
"Connection: close\r\n" + "Connection: close\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" + "Transfer-Encoding: chunked\r\n\r\n" +
chunk("abcdef") + chunk(""), chunk("abcdef") + chunk(""),
"POST http://www.google.com/search HTTP/1.1\r\n" + WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" + "Host: www.google.com\r\n" +
"User-Agent: Go http package\r\n" + "User-Agent: Go http package\r\n" +
"Connection: close\r\n" + "Connection: close\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" + "Transfer-Encoding: chunked\r\n\r\n" +
chunk("abcdef") + chunk(""), chunk("abcdef") + chunk(""),
nil,
}, },
// HTTP/1.1 POST with Content-Length, no chunking // HTTP/1.1 POST with Content-Length, no chunking
{ {
Request{ Req: Request{
Method: "POST", Method: "POST",
URL: &url.URL{ URL: &url.URL{
Scheme: "http", Scheme: "http",
...@@ -164,9 +165,9 @@ var reqWriteTests = []reqWriteTest{ ...@@ -164,9 +165,9 @@ var reqWriteTests = []reqWriteTest{
ContentLength: 6, ContentLength: 6,
}, },
[]byte("abcdef"), Body: []byte("abcdef"),
"POST /search HTTP/1.1\r\n" + WantWrite: "POST /search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" + "Host: www.google.com\r\n" +
"User-Agent: Go http package\r\n" + "User-Agent: Go http package\r\n" +
"Connection: close\r\n" + "Connection: close\r\n" +
...@@ -174,20 +175,18 @@ var reqWriteTests = []reqWriteTest{ ...@@ -174,20 +175,18 @@ var reqWriteTests = []reqWriteTest{
"\r\n" + "\r\n" +
"abcdef", "abcdef",
"POST http://www.google.com/search HTTP/1.1\r\n" + WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" + "Host: www.google.com\r\n" +
"User-Agent: Go http package\r\n" + "User-Agent: Go http package\r\n" +
"Connection: close\r\n" + "Connection: close\r\n" +
"Content-Length: 6\r\n" + "Content-Length: 6\r\n" +
"\r\n" + "\r\n" +
"abcdef", "abcdef",
nil,
}, },
// HTTP/1.1 POST with Content-Length in headers // HTTP/1.1 POST with Content-Length in headers
{ {
Request{ Req: Request{
Method: "POST", Method: "POST",
RawURL: "http://example.com/", RawURL: "http://example.com/",
Host: "example.com", Host: "example.com",
...@@ -197,52 +196,46 @@ var reqWriteTests = []reqWriteTest{ ...@@ -197,52 +196,46 @@ var reqWriteTests = []reqWriteTest{
ContentLength: 6, ContentLength: 6,
}, },
[]byte("abcdef"), Body: []byte("abcdef"),
"POST http://example.com/ HTTP/1.1\r\n" + WantWrite: "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" +
"\r\n" + "\r\n" +
"abcdef", "abcdef",
"POST http://example.com/ 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" +
"\r\n" + "\r\n" +
"abcdef", "abcdef",
nil,
}, },
// default to HTTP/1.1 // default to HTTP/1.1
{ {
Request{ Req: Request{
Method: "GET", Method: "GET",
RawURL: "/search", RawURL: "/search",
Host: "www.google.com", Host: "www.google.com",
}, },
nil, WantWrite: "GET /search HTTP/1.1\r\n" +
"GET /search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" + "Host: www.google.com\r\n" +
"User-Agent: Go http package\r\n" + "User-Agent: Go http package\r\n" +
"\r\n", "\r\n",
// Looks weird but RawURL overrides what WriteProxy would choose. // Looks weird but RawURL overrides what WriteProxy would choose.
"GET /search HTTP/1.1\r\n" + WantProxy: "GET /search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" + "Host: www.google.com\r\n" +
"User-Agent: Go http package\r\n" + "User-Agent: Go http package\r\n" +
"\r\n", "\r\n",
nil,
}, },
// Request with a 0 ContentLength and a 0 byte body. // Request with a 0 ContentLength and a 0 byte body.
{ {
Request{ Req: Request{
Method: "POST", Method: "POST",
RawURL: "/", RawURL: "/",
Host: "example.com", Host: "example.com",
...@@ -251,24 +244,22 @@ var reqWriteTests = []reqWriteTest{ ...@@ -251,24 +244,22 @@ var reqWriteTests = []reqWriteTest{
ContentLength: 0, // as if unset by user ContentLength: 0, // as if unset by user
}, },
func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) }, Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) },
"POST / HTTP/1.1\r\n" + WantWrite: "POST / 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" +
"\r\n", "\r\n",
"POST / HTTP/1.1\r\n" + WantProxy: "POST / 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" +
"\r\n", "\r\n",
nil,
}, },
// Request with a 0 ContentLength and a 1 byte body. // Request with a 0 ContentLength and a 1 byte body.
{ {
Request{ Req: Request{
Method: "POST", Method: "POST",
RawURL: "/", RawURL: "/",
Host: "example.com", Host: "example.com",
...@@ -277,26 +268,24 @@ var reqWriteTests = []reqWriteTest{ ...@@ -277,26 +268,24 @@ var reqWriteTests = []reqWriteTest{
ContentLength: 0, // as if unset by user ContentLength: 0, // as if unset by user
}, },
func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) }, Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) },
"POST / HTTP/1.1\r\n" + WantWrite: "POST / 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" +
"Transfer-Encoding: chunked\r\n\r\n" + "Transfer-Encoding: chunked\r\n\r\n" +
chunk("x") + chunk(""), chunk("x") + chunk(""),
"POST / HTTP/1.1\r\n" + WantProxy: "POST / 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" +
"Transfer-Encoding: chunked\r\n\r\n" + "Transfer-Encoding: chunked\r\n\r\n" +
chunk("x") + chunk(""), chunk("x") + chunk(""),
nil,
}, },
// Request with a ContentLength of 10 but a 5 byte body. // Request with a ContentLength of 10 but a 5 byte body.
{ {
Request{ Req: Request{
Method: "POST", Method: "POST",
RawURL: "/", RawURL: "/",
Host: "example.com", Host: "example.com",
...@@ -304,18 +293,13 @@ var reqWriteTests = []reqWriteTest{ ...@@ -304,18 +293,13 @@ var reqWriteTests = []reqWriteTest{
ProtoMinor: 1, ProtoMinor: 1,
ContentLength: 10, // but we're going to send only 5 bytes ContentLength: 10, // but we're going to send only 5 bytes
}, },
Body: []byte("12345"),
[]byte("12345"), WantError: os.NewError("http: Request.ContentLength=10 with Body length 5"),
"", // ignored
"", // ignored
os.NewError("http: Request.ContentLength=10 with Body length 5"),
}, },
// Request with a ContentLength of 4 but an 8 byte body. // Request with a ContentLength of 4 but an 8 byte body.
{ {
Request{ Req: Request{
Method: "POST", Method: "POST",
RawURL: "/", RawURL: "/",
Host: "example.com", Host: "example.com",
...@@ -323,18 +307,13 @@ var reqWriteTests = []reqWriteTest{ ...@@ -323,18 +307,13 @@ var reqWriteTests = []reqWriteTest{
ProtoMinor: 1, ProtoMinor: 1,
ContentLength: 4, // but we're going to try to send 8 bytes ContentLength: 4, // but we're going to try to send 8 bytes
}, },
Body: []byte("12345678"),
[]byte("12345678"), WantError: os.NewError("http: Request.ContentLength=4 with Body length 8"),
"", // ignored
"", // ignored
os.NewError("http: Request.ContentLength=4 with Body length 8"),
}, },
// Request with a 5 ContentLength and nil body. // Request with a 5 ContentLength and nil body.
{ {
Request{ Req: Request{
Method: "POST", Method: "POST",
RawURL: "/", RawURL: "/",
Host: "example.com", Host: "example.com",
...@@ -342,22 +321,30 @@ var reqWriteTests = []reqWriteTest{ ...@@ -342,22 +321,30 @@ var reqWriteTests = []reqWriteTest{
ProtoMinor: 1, ProtoMinor: 1,
ContentLength: 5, // but we'll omit the body ContentLength: 5, // but we'll omit the body
}, },
WantError: os.NewError("http: Request.ContentLength=5 with nil Body"),
},
nil, // missing body // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
// and doesn't add a User-Agent.
"POST / HTTP/1.1\r\n" + {
"Host: example.com\r\n" + Req: Request{
"User-Agent: Go http package\r\n" + Method: "GET",
"Content-Length: 5\r\n\r\n" + RawURL: "/foo",
"", ProtoMajor: 1,
ProtoMinor: 0,
Header: Header{
"X-Foo": []string{"X-Bar"},
},
},
"POST / HTTP/1.1\r\n" + // We can dump it:
"Host: example.com\r\n" + WantDump: "GET /foo HTTP/1.0\r\n" +
"User-Agent: Go http package\r\n" + "X-Foo: X-Bar\r\n\r\n",
"Content-Length: 5\r\n\r\n" +
"",
os.NewError("http: Request.ContentLength=5 with nil Body"), // .. but we can't call Request.Write on it, due to its lack of Host header.
// TODO(bradfitz): there might be an argument to allow this, but for now I'd
// rather let HTTP/1.0 continue to die.
WantError: os.NewError("http: Request.Write on Request with no Host or URL set"),
}, },
} }
...@@ -366,6 +353,9 @@ func TestRequestWrite(t *testing.T) { ...@@ -366,6 +353,9 @@ func TestRequestWrite(t *testing.T) {
tt := &reqWriteTests[i] tt := &reqWriteTests[i]
setBody := func() { setBody := func() {
if tt.Body == nil {
return
}
switch b := tt.Body.(type) { switch b := tt.Body.(type) {
case []byte: case []byte:
tt.Req.Body = ioutil.NopCloser(bytes.NewBuffer(b)) tt.Req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
...@@ -373,12 +363,11 @@ func TestRequestWrite(t *testing.T) { ...@@ -373,12 +363,11 @@ func TestRequestWrite(t *testing.T) {
tt.Req.Body = b() tt.Req.Body = b()
} }
} }
if tt.Body != nil { setBody()
setBody()
}
if tt.Req.Header == nil { if tt.Req.Header == nil {
tt.Req.Header = make(Header) tt.Req.Header = make(Header)
} }
var braw bytes.Buffer var braw bytes.Buffer
err := tt.Req.Write(&braw) err := tt.Req.Write(&braw)
if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e { if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e {
...@@ -389,25 +378,40 @@ func TestRequestWrite(t *testing.T) { ...@@ -389,25 +378,40 @@ func TestRequestWrite(t *testing.T) {
continue continue
} }
sraw := braw.String() if tt.WantWrite != "" {
if sraw != tt.Raw { sraw := braw.String()
t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.Raw, sraw) if sraw != tt.WantWrite {
continue t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw)
continue
}
} }
if tt.Body != nil { if tt.WantProxy != "" {
setBody() setBody()
var praw bytes.Buffer
err = tt.Req.WriteProxy(&praw)
if err != nil {
t.Errorf("WriteProxy #%d: %s", i, err)
continue
}
sraw := praw.String()
if sraw != tt.WantProxy {
t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw)
continue
}
} }
var praw bytes.Buffer
err = tt.Req.WriteProxy(&praw) if tt.WantDump != "" {
if err != nil { setBody()
t.Errorf("error writing #%d: %s", i, err) dump, err := DumpRequest(&tt.Req, true)
continue if err != nil {
} t.Errorf("DumpRequest #%d: %s", i, err)
sraw = praw.String() continue
if sraw != tt.RawProxy { }
t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.RawProxy, sraw) if string(dump) != tt.WantDump {
continue t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump))
continue
}
} }
} }
} }
......
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