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