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
5// HTTP Response reading and parsing.
6
7package http
8
9import (
10	"bufio"
11	"errors"
12	"io"
13	"net/textproto"
14	"net/url"
15	"strconv"
16	"strings"
17)
18
19var respExcludeHeader = map[string]bool{
20	"Content-Length":    true,
21	"Transfer-Encoding": true,
22	"Trailer":           true,
23}
24
25// Response represents the response from an HTTP request.
26//
27type Response struct {
28	Status     string // e.g. "200 OK"
29	StatusCode int    // e.g. 200
30	Proto      string // e.g. "HTTP/1.0"
31	ProtoMajor int    // e.g. 1
32	ProtoMinor int    // e.g. 0
33
34	// Header maps header keys to values.  If the response had multiple
35	// headers with the same key, they will be concatenated, with comma
36	// delimiters.  (Section 4.2 of RFC 2616 requires that multiple headers
37	// be semantically equivalent to a comma-delimited sequence.) Values
38	// duplicated by other fields in this struct (e.g., ContentLength) are
39	// omitted from Header.
40	//
41	// Keys in the map are canonicalized (see CanonicalHeaderKey).
42	Header Header
43
44	// Body represents the response body.
45	//
46	// The http Client and Transport guarantee that Body is always
47	// non-nil, even on responses without a body or responses with
48	// a zero-lengthed body.
49	Body io.ReadCloser
50
51	// ContentLength records the length of the associated content.  The
52	// value -1 indicates that the length is unknown.  Unless Request.Method
53	// is "HEAD", values >= 0 indicate that the given number of bytes may
54	// be read from Body.
55	ContentLength int64
56
57	// Contains transfer encodings from outer-most to inner-most. Value is
58	// nil, means that "identity" encoding is used.
59	TransferEncoding []string
60
61	// Close records whether the header directed that the connection be
62	// closed after reading Body.  The value is advice for clients: neither
63	// ReadResponse nor Response.Write ever closes a connection.
64	Close bool
65
66	// Trailer maps trailer keys to values, in the same
67	// format as the header.
68	Trailer Header
69
70	// The Request that was sent to obtain this Response.
71	// Request's Body is nil (having already been consumed).
72	// This is only populated for Client requests.
73	Request *Request
74}
75
76// Cookies parses and returns the cookies set in the Set-Cookie headers.
77func (r *Response) Cookies() []*Cookie {
78	return readSetCookies(r.Header)
79}
80
81var ErrNoLocation = errors.New("http: no Location header in response")
82
83// Location returns the URL of the response's "Location" header,
84// if present.  Relative redirects are resolved relative to
85// the Response's Request.  ErrNoLocation is returned if no
86// Location header is present.
87func (r *Response) Location() (*url.URL, error) {
88	lv := r.Header.Get("Location")
89	if lv == "" {
90		return nil, ErrNoLocation
91	}
92	if r.Request != nil && r.Request.URL != nil {
93		return r.Request.URL.Parse(lv)
94	}
95	return url.Parse(lv)
96}
97
98// ReadResponse reads and returns an HTTP response from r.  The
99// req parameter specifies the Request that corresponds to
100// this Response.  Clients must call resp.Body.Close when finished
101// reading resp.Body.  After that call, clients can inspect
102// resp.Trailer to find key/value pairs included in the response
103// trailer.
104func ReadResponse(r *bufio.Reader, req *Request) (resp *Response, err error) {
105
106	tp := textproto.NewReader(r)
107	resp = new(Response)
108
109	resp.Request = req
110
111	// Parse the first line of the response.
112	line, err := tp.ReadLine()
113	if err != nil {
114		if err == io.EOF {
115			err = io.ErrUnexpectedEOF
116		}
117		return nil, err
118	}
119	f := strings.SplitN(line, " ", 3)
120	if len(f) < 2 {
121		return nil, &badStringError{"malformed HTTP response", line}
122	}
123	reasonPhrase := ""
124	if len(f) > 2 {
125		reasonPhrase = f[2]
126	}
127	resp.Status = f[1] + " " + reasonPhrase
128	resp.StatusCode, err = strconv.Atoi(f[1])
129	if err != nil {
130		return nil, &badStringError{"malformed HTTP status code", f[1]}
131	}
132
133	resp.Proto = f[0]
134	var ok bool
135	if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok {
136		return nil, &badStringError{"malformed HTTP version", resp.Proto}
137	}
138
139	// Parse the response headers.
140	mimeHeader, err := tp.ReadMIMEHeader()
141	if err != nil {
142		return nil, err
143	}
144	resp.Header = Header(mimeHeader)
145
146	fixPragmaCacheControl(resp.Header)
147
148	err = readTransfer(resp, r)
149	if err != nil {
150		return nil, err
151	}
152
153	return resp, nil
154}
155
156// RFC2616: Should treat
157//	Pragma: no-cache
158// like
159//	Cache-Control: no-cache
160func fixPragmaCacheControl(header Header) {
161	if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
162		if _, presentcc := header["Cache-Control"]; !presentcc {
163			header["Cache-Control"] = []string{"no-cache"}
164		}
165	}
166}
167
168// ProtoAtLeast returns whether the HTTP protocol used
169// in the response is at least major.minor.
170func (r *Response) ProtoAtLeast(major, minor int) bool {
171	return r.ProtoMajor > major ||
172		r.ProtoMajor == major && r.ProtoMinor >= minor
173}
174
175// Writes the response (header, body and trailer) in wire format. This method
176// consults the following fields of the response:
177//
178//  StatusCode
179//  ProtoMajor
180//  ProtoMinor
181//  Request.Method
182//  TransferEncoding
183//  Trailer
184//  Body
185//  ContentLength
186//  Header, values for non-canonical keys will have unpredictable behavior
187//
188func (r *Response) Write(w io.Writer) error {
189
190	// Status line
191	text := r.Status
192	if text == "" {
193		var ok bool
194		text, ok = statusText[r.StatusCode]
195		if !ok {
196			text = "status code " + strconv.Itoa(r.StatusCode)
197		}
198	}
199	protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor)
200	statusCode := strconv.Itoa(r.StatusCode) + " "
201	if strings.HasPrefix(text, statusCode) {
202		text = text[len(statusCode):]
203	}
204	io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n")
205
206	// Process Body,ContentLength,Close,Trailer
207	tw, err := newTransferWriter(r)
208	if err != nil {
209		return err
210	}
211	err = tw.WriteHeader(w)
212	if err != nil {
213		return err
214	}
215
216	// Rest of header
217	err = r.Header.WriteSubset(w, respExcludeHeader)
218	if err != nil {
219		return err
220	}
221
222	// End-of-header
223	io.WriteString(w, "\r\n")
224
225	// Write body and trailer
226	err = tw.WriteBody(w)
227	if err != nil {
228		return err
229	}
230
231	// Success
232	return nil
233}
234