1// Copyright 2009 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	"bufio"
9	"bytes"
10	"errors"
11	"fmt"
12	"io"
13	"io/ioutil"
14	"net"
15	"net/http"
16	"net/url"
17	"strings"
18	"time"
19)
20
21// drainBody reads all of b to memory and then returns two equivalent
22// ReadClosers yielding the same bytes.
23//
24// It returns an error if the initial slurp of all bytes fails. It does not attempt
25// to make the returned ReadClosers have identical error-matching behavior.
26func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
27	if b == http.NoBody {
28		// No copying needed. Preserve the magic sentinel meaning of NoBody.
29		return http.NoBody, http.NoBody, nil
30	}
31	var buf bytes.Buffer
32	if _, err = buf.ReadFrom(b); err != nil {
33		return nil, b, err
34	}
35	if err = b.Close(); err != nil {
36		return nil, b, err
37	}
38	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
39}
40
41// dumpConn is a net.Conn which writes to Writer and reads from Reader
42type dumpConn struct {
43	io.Writer
44	io.Reader
45}
46
47func (c *dumpConn) Close() error                       { return nil }
48func (c *dumpConn) LocalAddr() net.Addr                { return nil }
49func (c *dumpConn) RemoteAddr() net.Addr               { return nil }
50func (c *dumpConn) SetDeadline(t time.Time) error      { return nil }
51func (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }
52func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
53
54type neverEnding byte
55
56func (b neverEnding) Read(p []byte) (n int, err error) {
57	for i := range p {
58		p[i] = byte(b)
59	}
60	return len(p), nil
61}
62
63// DumpRequestOut is like DumpRequest but for outgoing client requests. It
64// includes any headers that the standard http.Transport adds, such as
65// User-Agent.
66func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
67	save := req.Body
68	dummyBody := false
69	if !body || req.Body == nil {
70		req.Body = nil
71		if req.ContentLength != 0 {
72			req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength))
73			dummyBody = true
74		}
75	} else {
76		var err error
77		save, req.Body, err = drainBody(req.Body)
78		if err != nil {
79			return nil, err
80		}
81	}
82
83	// Since we're using the actual Transport code to write the request,
84	// switch to http so the Transport doesn't try to do an SSL
85	// negotiation with our dumpConn and its bytes.Buffer & pipe.
86	// The wire format for https and http are the same, anyway.
87	reqSend := req
88	if req.URL.Scheme == "https" {
89		reqSend = new(http.Request)
90		*reqSend = *req
91		reqSend.URL = new(url.URL)
92		*reqSend.URL = *req.URL
93		reqSend.URL.Scheme = "http"
94	}
95
96	// Use the actual Transport code to record what we would send
97	// on the wire, but not using TCP.  Use a Transport with a
98	// custom dialer that returns a fake net.Conn that waits
99	// for the full input (and recording it), and then responds
100	// with a dummy response.
101	var buf bytes.Buffer // records the output
102	pr, pw := io.Pipe()
103	defer pr.Close()
104	defer pw.Close()
105	dr := &delegateReader{c: make(chan io.Reader)}
106
107	t := &http.Transport{
108		Dial: func(net, addr string) (net.Conn, error) {
109			return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
110		},
111	}
112	defer t.CloseIdleConnections()
113
114	// Wait for the request before replying with a dummy response:
115	go func() {
116		req, err := http.ReadRequest(bufio.NewReader(pr))
117		if err == nil {
118			// Ensure all the body is read; otherwise
119			// we'll get a partial dump.
120			io.Copy(ioutil.Discard, req.Body)
121			req.Body.Close()
122		}
123		dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
124	}()
125
126	_, err := t.RoundTrip(reqSend)
127
128	req.Body = save
129	if err != nil {
130		return nil, err
131	}
132	dump := buf.Bytes()
133
134	// If we used a dummy body above, remove it now.
135	// TODO: if the req.ContentLength is large, we allocate memory
136	// unnecessarily just to slice it off here. But this is just
137	// a debug function, so this is acceptable for now. We could
138	// discard the body earlier if this matters.
139	if dummyBody {
140		if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
141			dump = dump[:i+4]
142		}
143	}
144	return dump, nil
145}
146
147// delegateReader is a reader that delegates to another reader,
148// once it arrives on a channel.
149type delegateReader struct {
150	c chan io.Reader
151	r io.Reader // nil until received from c
152}
153
154func (r *delegateReader) Read(p []byte) (int, error) {
155	if r.r == nil {
156		r.r = <-r.c
157	}
158	return r.r.Read(p)
159}
160
161// Return value if nonempty, def otherwise.
162func valueOrDefault(value, def string) string {
163	if value != "" {
164		return value
165	}
166	return def
167}
168
169var reqWriteExcludeHeaderDump = map[string]bool{
170	"Host":              true, // not in Header map anyway
171	"Transfer-Encoding": true,
172	"Trailer":           true,
173}
174
175// DumpRequest returns the given request in its HTTP/1.x wire
176// representation. It should only be used by servers to debug client
177// requests. The returned representation is an approximation only;
178// some details of the initial request are lost while parsing it into
179// an http.Request. In particular, the order and case of header field
180// names are lost. The order of values in multi-valued headers is kept
181// intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their
182// original binary representations.
183//
184// If body is true, DumpRequest also returns the body. To do so, it
185// consumes req.Body and then replaces it with a new io.ReadCloser
186// that yields the same bytes. If DumpRequest returns an error,
187// the state of req is undefined.
188//
189// The documentation for http.Request.Write details which fields
190// of req are included in the dump.
191func DumpRequest(req *http.Request, body bool) ([]byte, error) {
192	var err error
193	save := req.Body
194	if !body || req.Body == nil {
195		req.Body = nil
196	} else {
197		save, req.Body, err = drainBody(req.Body)
198		if err != nil {
199			return nil, err
200		}
201	}
202
203	var b bytes.Buffer
204
205	// By default, print out the unmodified req.RequestURI, which
206	// is always set for incoming server requests. But because we
207	// previously used req.URL.RequestURI and the docs weren't
208	// always so clear about when to use DumpRequest vs
209	// DumpRequestOut, fall back to the old way if the caller
210	// provides a non-server Request.
211	reqURI := req.RequestURI
212	if reqURI == "" {
213		reqURI = req.URL.RequestURI()
214	}
215
216	fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
217		reqURI, req.ProtoMajor, req.ProtoMinor)
218
219	absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
220	if !absRequestURI {
221		host := req.Host
222		if host == "" && req.URL != nil {
223			host = req.URL.Host
224		}
225		if host != "" {
226			fmt.Fprintf(&b, "Host: %s\r\n", host)
227		}
228	}
229
230	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
231	if len(req.TransferEncoding) > 0 {
232		fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
233	}
234	if req.Close {
235		fmt.Fprintf(&b, "Connection: close\r\n")
236	}
237
238	err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
239	if err != nil {
240		return nil, err
241	}
242
243	io.WriteString(&b, "\r\n")
244
245	if req.Body != nil {
246		var dest io.Writer = &b
247		if chunked {
248			dest = NewChunkedWriter(dest)
249		}
250		_, err = io.Copy(dest, req.Body)
251		if chunked {
252			dest.(io.Closer).Close()
253			io.WriteString(&b, "\r\n")
254		}
255	}
256
257	req.Body = save
258	if err != nil {
259		return nil, err
260	}
261	return b.Bytes(), nil
262}
263
264// errNoBody is a sentinel error value used by failureToReadBody so we
265// can detect that the lack of body was intentional.
266var errNoBody = errors.New("sentinel error value")
267
268// failureToReadBody is a io.ReadCloser that just returns errNoBody on
269// Read. It's swapped in when we don't actually want to consume
270// the body, but need a non-nil one, and want to distinguish the
271// error from reading the dummy body.
272type failureToReadBody struct{}
273
274func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
275func (failureToReadBody) Close() error             { return nil }
276
277// emptyBody is an instance of empty reader.
278var emptyBody = ioutil.NopCloser(strings.NewReader(""))
279
280// DumpResponse is like DumpRequest but dumps a response.
281func DumpResponse(resp *http.Response, body bool) ([]byte, error) {
282	var b bytes.Buffer
283	var err error
284	save := resp.Body
285	savecl := resp.ContentLength
286
287	if !body {
288		// For content length of zero. Make sure the body is an empty
289		// reader, instead of returning error through failureToReadBody{}.
290		if resp.ContentLength == 0 {
291			resp.Body = emptyBody
292		} else {
293			resp.Body = failureToReadBody{}
294		}
295	} else if resp.Body == nil {
296		resp.Body = emptyBody
297	} else {
298		save, resp.Body, err = drainBody(resp.Body)
299		if err != nil {
300			return nil, err
301		}
302	}
303	err = resp.Write(&b)
304	if err == errNoBody {
305		err = nil
306	}
307	resp.Body = save
308	resp.ContentLength = savecl
309	if err != nil {
310		return nil, err
311	}
312	return b.Bytes(), nil
313}
314