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	"fmt"
11	"io"
12	"io/ioutil"
13	"net"
14	"net/http"
15	"net/url"
16	"strings"
17	"time"
18)
19
20// One of the copies, say from b to r2, could be avoided by using a more
21// elaborate trick where the other copy is made during Request/Response.Write.
22// This would complicate things too much, given that these functions are for
23// debugging only.
24func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
25	var buf bytes.Buffer
26	if _, err = buf.ReadFrom(b); err != nil {
27		return nil, nil, err
28	}
29	if err = b.Close(); err != nil {
30		return nil, nil, err
31	}
32	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewBuffer(buf.Bytes())), nil
33}
34
35// dumpConn is a net.Conn which writes to Writer and reads from Reader
36type dumpConn struct {
37	io.Writer
38	io.Reader
39}
40
41func (c *dumpConn) Close() error                       { return nil }
42func (c *dumpConn) LocalAddr() net.Addr                { return nil }
43func (c *dumpConn) RemoteAddr() net.Addr               { return nil }
44func (c *dumpConn) SetDeadline(t time.Time) error      { return nil }
45func (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }
46func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
47
48// DumpRequestOut is like DumpRequest but includes
49// headers that the standard http.Transport adds,
50// such as User-Agent.
51func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
52	save := req.Body
53	if !body || req.Body == nil {
54		req.Body = nil
55	} else {
56		var err error
57		save, req.Body, err = drainBody(req.Body)
58		if err != nil {
59			return nil, err
60		}
61	}
62
63	// Since we're using the actual Transport code to write the request,
64	// switch to http so the Transport doesn't try to do an SSL
65	// negotiation with our dumpConn and its bytes.Buffer & pipe.
66	// The wire format for https and http are the same, anyway.
67	reqSend := req
68	if req.URL.Scheme == "https" {
69		reqSend = new(http.Request)
70		*reqSend = *req
71		reqSend.URL = new(url.URL)
72		*reqSend.URL = *req.URL
73		reqSend.URL.Scheme = "http"
74	}
75
76	// Use the actual Transport code to record what we would send
77	// on the wire, but not using TCP.  Use a Transport with a
78	// custom dialer that returns a fake net.Conn that waits
79	// for the full input (and recording it), and then responds
80	// with a dummy response.
81	var buf bytes.Buffer // records the output
82	pr, pw := io.Pipe()
83	dr := &delegateReader{c: make(chan io.Reader)}
84	// Wait for the request before replying with a dummy response:
85	go func() {
86		http.ReadRequest(bufio.NewReader(pr))
87		dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n")
88	}()
89
90	t := &http.Transport{
91		Dial: func(net, addr string) (net.Conn, error) {
92			return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
93		},
94	}
95
96	_, err := t.RoundTrip(reqSend)
97
98	req.Body = save
99	if err != nil {
100		return nil, err
101	}
102	return buf.Bytes(), nil
103}
104
105// delegateReader is a reader that delegates to another reader,
106// once it arrives on a channel.
107type delegateReader struct {
108	c chan io.Reader
109	r io.Reader // nil until received from c
110}
111
112func (r *delegateReader) Read(p []byte) (int, error) {
113	if r.r == nil {
114		r.r = <-r.c
115	}
116	return r.r.Read(p)
117}
118
119// Return value if nonempty, def otherwise.
120func valueOrDefault(value, def string) string {
121	if value != "" {
122		return value
123	}
124	return def
125}
126
127var reqWriteExcludeHeaderDump = map[string]bool{
128	"Host":              true, // not in Header map anyway
129	"Content-Length":    true,
130	"Transfer-Encoding": true,
131	"Trailer":           true,
132}
133
134// dumpAsReceived writes req to w in the form as it was received, or
135// at least as accurately as possible from the information retained in
136// the request.
137func dumpAsReceived(req *http.Request, w io.Writer) error {
138	return nil
139}
140
141// DumpRequest returns the as-received wire representation of req,
142// optionally including the request body, for debugging.
143// DumpRequest is semantically a no-op, but in order to
144// dump the body, it reads the body data into memory and
145// changes req.Body to refer to the in-memory copy.
146// The documentation for http.Request.Write details which fields
147// of req are used.
148func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
149	save := req.Body
150	if !body || req.Body == nil {
151		req.Body = nil
152	} else {
153		save, req.Body, err = drainBody(req.Body)
154		if err != nil {
155			return
156		}
157	}
158
159	var b bytes.Buffer
160
161	fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
162		req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor)
163
164	host := req.Host
165	if host == "" && req.URL != nil {
166		host = req.URL.Host
167	}
168	if host != "" {
169		fmt.Fprintf(&b, "Host: %s\r\n", host)
170	}
171
172	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
173	if len(req.TransferEncoding) > 0 {
174		fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
175	}
176	if req.Close {
177		fmt.Fprintf(&b, "Connection: close\r\n")
178	}
179
180	err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
181	if err != nil {
182		return
183	}
184
185	io.WriteString(&b, "\r\n")
186
187	if req.Body != nil {
188		var dest io.Writer = &b
189		if chunked {
190			dest = NewChunkedWriter(dest)
191		}
192		_, err = io.Copy(dest, req.Body)
193		if chunked {
194			dest.(io.Closer).Close()
195			io.WriteString(&b, "\r\n")
196		}
197	}
198
199	req.Body = save
200	if err != nil {
201		return
202	}
203	dump = b.Bytes()
204	return
205}
206
207// DumpResponse is like DumpRequest but dumps a response.
208func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) {
209	var b bytes.Buffer
210	save := resp.Body
211	savecl := resp.ContentLength
212	if !body || resp.Body == nil {
213		resp.Body = nil
214		resp.ContentLength = 0
215	} else {
216		save, resp.Body, err = drainBody(resp.Body)
217		if err != nil {
218			return
219		}
220	}
221	err = resp.Write(&b)
222	resp.Body = save
223	resp.ContentLength = savecl
224	if err != nil {
225		return
226	}
227	dump = b.Bytes()
228	return
229}
230