1
0
mirror of https://github.com/golang/go synced 2024-11-22 07:34:40 -07:00

http: prevent DumpRequest from adding implicit headers

Fixes #2272

R=rsc
CC=golang-dev
https://golang.org/cl/5043051
This commit is contained in:
Brad Fitzpatrick 2011-09-19 10:22:53 -07:00
parent 24257a1ea2
commit 6b6cb725e9
3 changed files with 162 additions and 104 deletions

View File

@ -44,7 +44,7 @@ func DumpRequest(req *Request, body bool) (dump []byte, err os.Error) {
return
}
}
err = req.Write(&b)
err = req.dumpWrite(&b)
req.Body = save
if err != nil {
return

View File

@ -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.
var reqWriteExcludeHeader = map[string]bool{
"Host": true,
"Host": true, // not in Header map anyway
"User-Agent": true,
"Content-Length": true,
"Transfer-Encoding": 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.
type Request struct {
Method string // GET, POST, PUT, etc.
@ -283,6 +290,53 @@ func (req *Request) WriteProxy(w io.Writer) os.Error {
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 {
host := req.Host
if host == "" {

View File

@ -18,15 +18,19 @@ import (
type reqWriteTest struct {
Req Request
Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
Raw string
RawProxy string
WantError os.Error
// Any of these three may be empty to skip that test.
WantWrite string // Request.Write
WantProxy string // Request.WriteProxy
WantDump string // DumpRequest
WantError os.Error // wanted error from Request.Write
}
var reqWriteTests = []reqWriteTest{
// HTTP/1.1 => chunked coding; no body; no trailer
{
Request{
Req: Request{
Method: "GET",
RawURL: "http://www.techcrunch.com/",
URL: &url.URL{
@ -58,9 +62,7 @@ var reqWriteTests = []reqWriteTest{
Form: map[string][]string{},
},
nil,
"GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
WantWrite: "GET http://www.techcrunch.com/ 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" +
@ -70,7 +72,7 @@ var reqWriteTests = []reqWriteTest{
"Keep-Alive: 300\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" +
"User-Agent: Fake\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{
"Accept-Language: en-us,en;q=0.5\r\n" +
"Keep-Alive: 300\r\n" +
"Proxy-Connection: keep-alive\r\n\r\n",
nil,
},
// HTTP/1.1 => chunked coding; body; empty trailer
{
Request{
Req: Request{
Method: "GET",
URL: &url.URL{
Scheme: "http",
@ -97,25 +97,28 @@ var reqWriteTests = []reqWriteTest{
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" +
"User-Agent: Go http package\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
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" +
"User-Agent: Go http package\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
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
{
Request{
Req: Request{
Method: "POST",
URL: &url.URL{
Scheme: "http",
@ -129,28 +132,26 @@ var reqWriteTests = []reqWriteTest{
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" +
"User-Agent: Go http package\r\n" +
"Connection: close\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
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" +
"User-Agent: Go http package\r\n" +
"Connection: close\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
chunk("abcdef") + chunk(""),
nil,
},
// HTTP/1.1 POST with Content-Length, no chunking
{
Request{
Req: Request{
Method: "POST",
URL: &url.URL{
Scheme: "http",
@ -164,9 +165,9 @@ var reqWriteTests = []reqWriteTest{
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" +
"User-Agent: Go http package\r\n" +
"Connection: close\r\n" +
@ -174,20 +175,18 @@ var reqWriteTests = []reqWriteTest{
"\r\n" +
"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" +
"User-Agent: Go http package\r\n" +
"Connection: close\r\n" +
"Content-Length: 6\r\n" +
"\r\n" +
"abcdef",
nil,
},
// HTTP/1.1 POST with Content-Length in headers
{
Request{
Req: Request{
Method: "POST",
RawURL: "http://example.com/",
Host: "example.com",
@ -197,52 +196,46 @@ var reqWriteTests = []reqWriteTest{
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" +
"User-Agent: Go http package\r\n" +
"Content-Length: 6\r\n" +
"\r\n" +
"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" +
"User-Agent: Go http package\r\n" +
"Content-Length: 6\r\n" +
"\r\n" +
"abcdef",
nil,
},
// default to HTTP/1.1
{
Request{
Req: Request{
Method: "GET",
RawURL: "/search",
Host: "www.google.com",
},
nil,
"GET /search HTTP/1.1\r\n" +
WantWrite: "GET /search HTTP/1.1\r\n" +
"Host: www.google.com\r\n" +
"User-Agent: Go http package\r\n" +
"\r\n",
// 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" +
"User-Agent: Go http package\r\n" +
"\r\n",
nil,
},
// Request with a 0 ContentLength and a 0 byte body.
{
Request{
Req: Request{
Method: "POST",
RawURL: "/",
Host: "example.com",
@ -251,24 +244,22 @@ var reqWriteTests = []reqWriteTest{
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" +
"User-Agent: Go http package\r\n" +
"\r\n",
"POST / HTTP/1.1\r\n" +
WantProxy: "POST / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"User-Agent: Go http package\r\n" +
"\r\n",
nil,
},
// Request with a 0 ContentLength and a 1 byte body.
{
Request{
Req: Request{
Method: "POST",
RawURL: "/",
Host: "example.com",
@ -277,26 +268,24 @@ var reqWriteTests = []reqWriteTest{
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" +
"User-Agent: Go http package\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
chunk("x") + chunk(""),
"POST / HTTP/1.1\r\n" +
WantProxy: "POST / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"User-Agent: Go http package\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
chunk("x") + chunk(""),
nil,
},
// Request with a ContentLength of 10 but a 5 byte body.
{
Request{
Req: Request{
Method: "POST",
RawURL: "/",
Host: "example.com",
@ -304,18 +293,13 @@ var reqWriteTests = []reqWriteTest{
ProtoMinor: 1,
ContentLength: 10, // but we're going to send only 5 bytes
},
[]byte("12345"),
"", // ignored
"", // ignored
os.NewError("http: Request.ContentLength=10 with Body length 5"),
Body: []byte("12345"),
WantError: os.NewError("http: Request.ContentLength=10 with Body length 5"),
},
// Request with a ContentLength of 4 but an 8 byte body.
{
Request{
Req: Request{
Method: "POST",
RawURL: "/",
Host: "example.com",
@ -323,18 +307,13 @@ var reqWriteTests = []reqWriteTest{
ProtoMinor: 1,
ContentLength: 4, // but we're going to try to send 8 bytes
},
[]byte("12345678"),
"", // ignored
"", // ignored
os.NewError("http: Request.ContentLength=4 with Body length 8"),
Body: []byte("12345678"),
WantError: os.NewError("http: Request.ContentLength=4 with Body length 8"),
},
// Request with a 5 ContentLength and nil body.
{
Request{
Req: Request{
Method: "POST",
RawURL: "/",
Host: "example.com",
@ -342,22 +321,30 @@ var reqWriteTests = []reqWriteTest{
ProtoMinor: 1,
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.
{
Req: Request{
Method: "GET",
RawURL: "/foo",
ProtoMajor: 1,
ProtoMinor: 0,
Header: Header{
"X-Foo": []string{"X-Bar"},
},
},
"POST / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"User-Agent: Go http package\r\n" +
"Content-Length: 5\r\n\r\n" +
"",
// We can dump it:
WantDump: "GET /foo HTTP/1.0\r\n" +
"X-Foo: X-Bar\r\n\r\n",
"POST / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"User-Agent: Go http package\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) {
tt := &reqWriteTests[i]
setBody := func() {
if tt.Body == nil {
return
}
switch b := tt.Body.(type) {
case []byte:
tt.Req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
@ -373,12 +363,11 @@ func TestRequestWrite(t *testing.T) {
tt.Req.Body = b()
}
}
if tt.Body != nil {
setBody()
}
if tt.Req.Header == nil {
tt.Req.Header = make(Header)
}
var braw bytes.Buffer
err := tt.Req.Write(&braw)
if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e {
@ -389,27 +378,42 @@ func TestRequestWrite(t *testing.T) {
continue
}
if tt.WantWrite != "" {
sraw := braw.String()
if sraw != tt.Raw {
t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.Raw, sraw)
if sraw != tt.WantWrite {
t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw)
continue
}
if tt.Body != nil {
setBody()
}
if tt.WantProxy != "" {
setBody()
var praw bytes.Buffer
err = tt.Req.WriteProxy(&praw)
if err != nil {
t.Errorf("error writing #%d: %s", i, err)
t.Errorf("WriteProxy #%d: %s", i, err)
continue
}
sraw = praw.String()
if sraw != tt.RawProxy {
t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.RawProxy, sraw)
sraw := praw.String()
if sraw != tt.WantProxy {
t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw)
continue
}
}
if tt.WantDump != "" {
setBody()
dump, err := DumpRequest(&tt.Req, true)
if err != nil {
t.Errorf("DumpRequest #%d: %s", i, err)
continue
}
if string(dump) != tt.WantDump {
t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump))
continue
}
}
}
}
type closeChecker struct {