1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package httputil
6
7import (
8	"bytes"
9	"fmt"
10	"io"
11	"io/ioutil"
12	"net/http"
13	"net/url"
14	"testing"
15)
16
17type dumpTest struct {
18	Req  http.Request
19	Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
20
21	WantDump    string
22	WantDumpOut string
23	NoBody      bool // if true, set DumpRequest{,Out} body to false
24}
25
26var dumpTests = []dumpTest{
27
28	// HTTP/1.1 => chunked coding; body; empty trailer
29	{
30		Req: http.Request{
31			Method: "GET",
32			URL: &url.URL{
33				Scheme: "http",
34				Host:   "www.google.com",
35				Path:   "/search",
36			},
37			ProtoMajor:       1,
38			ProtoMinor:       1,
39			TransferEncoding: []string{"chunked"},
40		},
41
42		Body: []byte("abcdef"),
43
44		WantDump: "GET /search HTTP/1.1\r\n" +
45			"Host: www.google.com\r\n" +
46			"Transfer-Encoding: chunked\r\n\r\n" +
47			chunk("abcdef") + chunk(""),
48	},
49
50	// Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
51	// and doesn't add a User-Agent.
52	{
53		Req: http.Request{
54			Method:     "GET",
55			URL:        mustParseURL("/foo"),
56			ProtoMajor: 1,
57			ProtoMinor: 0,
58			Header: http.Header{
59				"X-Foo": []string{"X-Bar"},
60			},
61		},
62
63		WantDump: "GET /foo HTTP/1.0\r\n" +
64			"X-Foo: X-Bar\r\n\r\n",
65	},
66
67	{
68		Req: *mustNewRequest("GET", "http://example.com/foo", nil),
69
70		WantDumpOut: "GET /foo HTTP/1.1\r\n" +
71			"Host: example.com\r\n" +
72			"User-Agent: Go 1.1 package http\r\n" +
73			"Accept-Encoding: gzip\r\n\r\n",
74	},
75
76	// Test that an https URL doesn't try to do an SSL negotiation
77	// with a bytes.Buffer and hang with all goroutines not
78	// runnable.
79	{
80		Req: *mustNewRequest("GET", "https://example.com/foo", nil),
81
82		WantDumpOut: "GET /foo HTTP/1.1\r\n" +
83			"Host: example.com\r\n" +
84			"User-Agent: Go 1.1 package http\r\n" +
85			"Accept-Encoding: gzip\r\n\r\n",
86	},
87
88	// Request with Body, but Dump requested without it.
89	{
90		Req: http.Request{
91			Method: "POST",
92			URL: &url.URL{
93				Scheme: "http",
94				Host:   "post.tld",
95				Path:   "/",
96			},
97			ContentLength: 6,
98			ProtoMajor:    1,
99			ProtoMinor:    1,
100		},
101
102		Body: []byte("abcdef"),
103
104		WantDumpOut: "POST / HTTP/1.1\r\n" +
105			"Host: post.tld\r\n" +
106			"User-Agent: Go 1.1 package http\r\n" +
107			"Content-Length: 6\r\n" +
108			"Accept-Encoding: gzip\r\n\r\n",
109
110		NoBody: true,
111	},
112}
113
114func TestDumpRequest(t *testing.T) {
115	for i, tt := range dumpTests {
116		setBody := func() {
117			if tt.Body == nil {
118				return
119			}
120			switch b := tt.Body.(type) {
121			case []byte:
122				tt.Req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
123			case func() io.ReadCloser:
124				tt.Req.Body = b()
125			}
126		}
127		setBody()
128		if tt.Req.Header == nil {
129			tt.Req.Header = make(http.Header)
130		}
131
132		if tt.WantDump != "" {
133			setBody()
134			dump, err := DumpRequest(&tt.Req, !tt.NoBody)
135			if err != nil {
136				t.Errorf("DumpRequest #%d: %s", i, err)
137				continue
138			}
139			if string(dump) != tt.WantDump {
140				t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump))
141				continue
142			}
143		}
144
145		if tt.WantDumpOut != "" {
146			setBody()
147			dump, err := DumpRequestOut(&tt.Req, !tt.NoBody)
148			if err != nil {
149				t.Errorf("DumpRequestOut #%d: %s", i, err)
150				continue
151			}
152			if string(dump) != tt.WantDumpOut {
153				t.Errorf("DumpRequestOut %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDumpOut, string(dump))
154				continue
155			}
156		}
157	}
158}
159
160func chunk(s string) string {
161	return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
162}
163
164func mustParseURL(s string) *url.URL {
165	u, err := url.Parse(s)
166	if err != nil {
167		panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
168	}
169	return u
170}
171
172func mustNewRequest(method, url string, body io.Reader) *http.Request {
173	req, err := http.NewRequest(method, url, body)
174	if err != nil {
175		panic(fmt.Sprintf("NewRequest(%q, %q, %p) err = %v", method, url, body, err))
176	}
177	return req
178}
179