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.
20 // transferWriter inspects the fields of a user-supplied Request or Response,
21 // sanitizes them without changing the user object and provides methods for
22 // writing the respective header, body and trailer in wire format.
23 type transferWriter
struct {
28 ContentLength
int64 // -1 means unknown, 0 means exactly none
30 TransferEncoding
[]string
34 func newTransferWriter(r
interface{}) (t
*transferWriter
, err error
) {
37 // Extract relevant fields
38 atLeastHTTP11
:= false
39 switch rr
:= r
.(type) {
41 if rr
.ContentLength
!= 0 && rr
.Body
== nil {
42 return nil, fmt
.Errorf("http: Request.ContentLength=%d with nil Body", rr
.ContentLength
)
46 t
.BodyCloser
= rr
.Body
47 t
.ContentLength
= rr
.ContentLength
49 t
.TransferEncoding
= rr
.TransferEncoding
50 t
.Trailer
= rr
.Trailer
51 atLeastHTTP11
= rr
.ProtoAtLeast(1, 1)
52 if t
.Body
!= nil && len(t
.TransferEncoding
) == 0 && atLeastHTTP11
{
53 if t
.ContentLength
== 0 {
54 // Test to see if it's actually zero or just unset.
56 n
, _
:= io
.ReadFull(t
.Body
, buf
[:])
58 // Oh, guess there is data in this Body Reader after all.
59 // The ContentLength field just wasn't set.
60 // Stich the Body back together again, re-attaching our
63 t
.Body
= io
.MultiReader(bytes
.NewBuffer(buf
[:]), t
.Body
)
65 // Body is actually empty.
70 if t
.ContentLength
< 0 {
71 t
.TransferEncoding
= []string{"chunked"}
75 if rr
.Request
!= nil {
76 t
.Method
= rr
.Request
.Method
79 t
.BodyCloser
= rr
.Body
80 t
.ContentLength
= rr
.ContentLength
82 t
.TransferEncoding
= rr
.TransferEncoding
83 t
.Trailer
= rr
.Trailer
84 atLeastHTTP11
= rr
.ProtoAtLeast(1, 1)
85 t
.ResponseToHEAD
= noBodyExpected(t
.Method
)
88 // Sanitize Body,ContentLength,TransferEncoding
91 if chunked(t
.TransferEncoding
) {
95 if !atLeastHTTP11 || t
.Body
== nil {
96 t
.TransferEncoding
= nil
98 if chunked(t
.TransferEncoding
) {
100 } else if t
.Body
== nil { // no chunking, no body
106 if !chunked(t
.TransferEncoding
) {
113 func noBodyExpected(requestMethod
string) bool {
114 return requestMethod
== "HEAD"
117 func (t
*transferWriter
) shouldSendContentLength() bool {
118 if chunked(t
.TransferEncoding
) {
121 if t
.ContentLength
> 0 {
124 // Many servers expect a Content-Length for these methods
125 if t
.Method
== "POST" || t
.Method
== "PUT" {
128 if t
.ContentLength
== 0 && isIdentity(t
.TransferEncoding
) {
135 func (t
*transferWriter
) WriteHeader(w io
.Writer
) (err error
) {
137 _
, err
= io
.WriteString(w
, "Connection: close\r\n")
143 // Write Content-Length and/or Transfer-Encoding whose values are a
144 // function of the sanitized field triple (Body, ContentLength,
146 if t
.shouldSendContentLength() {
147 io
.WriteString(w
, "Content-Length: ")
148 _
, err
= io
.WriteString(w
, strconv
.FormatInt(t
.ContentLength
, 10)+"\r\n")
152 } else if chunked(t
.TransferEncoding
) {
153 _
, err
= io
.WriteString(w
, "Transfer-Encoding: chunked\r\n")
159 // Write Trailer header
160 if t
.Trailer
!= nil {
161 // TODO: At some point, there should be a generic mechanism for
162 // writing long headers, using HTTP line splitting
163 io
.WriteString(w
, "Trailer: ")
165 for k
:= range t
.Trailer
{
166 k
= CanonicalHeaderKey(k
)
168 case "Transfer-Encoding", "Trailer", "Content-Length":
169 return &badStringError
{"invalid Trailer key", k
}
172 io
.WriteString(w
, ",")
177 _
, err
= io
.WriteString(w
, "\r\n")
183 func (t
*transferWriter
) WriteBody(w io
.Writer
) (err error
) {
188 if chunked(t
.TransferEncoding
) {
189 cw
:= newChunkedWriter(w
)
190 _
, err
= io
.Copy(cw
, t
.Body
)
194 } else if t
.ContentLength
== -1 {
195 ncopy
, err
= io
.Copy(w
, t
.Body
)
197 ncopy
, err
= io
.Copy(w
, io
.LimitReader(t
.Body
, t
.ContentLength
))
202 nextra
, err
= io
.Copy(ioutil
.Discard
, t
.Body
)
208 if err
= t
.BodyCloser
.Close(); err
!= nil {
213 if !t
.ResponseToHEAD
&& t
.ContentLength
!= -1 && t
.ContentLength
!= ncopy
{
214 return fmt
.Errorf("http: Request.ContentLength=%d with Body length %d",
215 t
.ContentLength
, ncopy
)
218 // TODO(petar): Place trailer writer code here.
219 if chunked(t
.TransferEncoding
) {
220 // Last chunk, empty trailer
221 _
, err
= io
.WriteString(w
, "\r\n")
227 type transferReader
struct {
237 TransferEncoding
[]string
242 // bodyAllowedForStatus reports whether a given response status code
243 // permits a body. See RFC2616, section 4.4.
244 func bodyAllowedForStatus(status
int) bool {
246 case status
>= 100 && status
<= 199:
256 // msg is *Request or *Response.
257 func readTransfer(msg
interface{}, r
*bufio
.Reader
) (err error
) {
258 t
:= &transferReader
{RequestMethod
: "GET"}
262 switch rr
:= msg
.(type) {
265 t
.StatusCode
= rr
.StatusCode
266 t
.ProtoMajor
= rr
.ProtoMajor
267 t
.ProtoMinor
= rr
.ProtoMinor
268 t
.Close
= shouldClose(t
.ProtoMajor
, t
.ProtoMinor
, t
.Header
)
270 if rr
.Request
!= nil {
271 t
.RequestMethod
= rr
.Request
.Method
275 t
.ProtoMajor
= rr
.ProtoMajor
276 t
.ProtoMinor
= rr
.ProtoMinor
277 // Transfer semantics for Requests are exactly like those for
278 // Responses with status code 200, responding to a GET method
281 panic("unexpected type")
284 // Default to HTTP/1.1
285 if t
.ProtoMajor
== 0 && t
.ProtoMinor
== 0 {
286 t
.ProtoMajor
, t
.ProtoMinor
= 1, 1
289 // Transfer encoding, content length
290 t
.TransferEncoding
, err
= fixTransferEncoding(t
.RequestMethod
, t
.Header
)
295 realLength
, err
:= fixLength(isResponse
, t
.StatusCode
, t
.RequestMethod
, t
.Header
, t
.TransferEncoding
)
299 if isResponse
&& t
.RequestMethod
== "HEAD" {
300 if n
, err
:= parseContentLength(t
.Header
.get("Content-Length")); err
!= nil {
306 t
.ContentLength
= realLength
310 t
.Trailer
, err
= fixTrailer(t
.Header
, t
.TransferEncoding
)
315 // If there is no Content-Length or chunked Transfer-Encoding on a *Response
316 // and the status is not 1xx, 204 or 304, then the body is unbounded.
317 // See RFC2616, section 4.4.
320 if realLength
== -1 &&
321 !chunked(t
.TransferEncoding
) &&
322 bodyAllowedForStatus(t
.StatusCode
) {
328 // Prepare body reader. ContentLength < 0 means chunked encoding
329 // or close connection when finished, since multipart is not supported yet
331 case chunked(t
.TransferEncoding
):
332 if noBodyExpected(t
.RequestMethod
) {
335 t
.Body
= &body
{src
: newChunkedReader(r
), hdr
: msg
, r
: r
, closing
: t
.Close
}
337 case realLength
== 0:
340 t
.Body
= &body
{src
: io
.LimitReader(r
, realLength
), closing
: t
.Close
}
342 // realLength < 0, i.e. "Content-Length" not mentioned in header
344 // Close semantics (i.e. HTTP/1.0)
345 t
.Body
= &body
{src
: r
, closing
: t
.Close
}
347 // Persistent connection (i.e. HTTP/1.1)
353 switch rr
:= msg
.(type) {
356 rr
.ContentLength
= t
.ContentLength
357 rr
.TransferEncoding
= t
.TransferEncoding
359 rr
.Trailer
= t
.Trailer
362 rr
.ContentLength
= t
.ContentLength
363 rr
.TransferEncoding
= t
.TransferEncoding
365 rr
.Trailer
= t
.Trailer
371 // Checks whether chunked is part of the encodings stack
372 func chunked(te
[]string) bool { return len(te
) > 0 && te
[0] == "chunked" }
374 // Checks whether the encoding is explicitly "identity".
375 func isIdentity(te
[]string) bool { return len(te
) == 1 && te
[0] == "identity" }
377 // Sanitize transfer encoding
378 func fixTransferEncoding(requestMethod
string, header Header
) ([]string, error
) {
379 raw
, present
:= header
["Transfer-Encoding"]
384 delete(header
, "Transfer-Encoding")
386 encodings
:= strings
.Split(raw
[0], ",")
387 te
:= make([]string, 0, len(encodings
))
388 // TODO: Even though we only support "identity" and "chunked"
389 // encodings, the loop below is designed with foresight. One
390 // invariant that must be maintained is that, if present,
391 // chunked encoding must always come first.
392 for _
, encoding
:= range encodings
{
393 encoding
= strings
.ToLower(strings
.TrimSpace(encoding
))
394 // "identity" encoding is not recorded
395 if encoding
== "identity" {
398 if encoding
!= "chunked" {
399 return nil, &badStringError
{"unsupported transfer encoding", encoding
}
401 te
= te
[0 : len(te
)+1]
402 te
[len(te
)-1] = encoding
405 return nil, &badStringError
{"too many transfer encodings", strings
.Join(te
, ",")}
408 // Chunked encoding trumps Content-Length. See RFC 2616
409 // Section 4.4. Currently len(te) > 0 implies chunked
411 delete(header
, "Content-Length")
418 // Determine the expected body length, using RFC 2616 Section 4.4. This
419 // function is not a method, because ultimately it should be shared by
420 // ReadResponse and ReadRequest.
421 func fixLength(isResponse
bool, status
int, requestMethod
string, header Header
, te
[]string) (int64, error
) {
423 // Logic based on response type or status
424 if noBodyExpected(requestMethod
) {
435 // Logic based on Transfer-Encoding
440 // Logic based on Content-Length
441 cl
:= strings
.TrimSpace(header
.get("Content-Length"))
443 n
, err
:= parseContentLength(cl
)
449 header
.Del("Content-Length")
452 if !isResponse
&& requestMethod
== "GET" {
453 // RFC 2616 doesn't explicitly permit nor forbid an
454 // entity-body on a GET request so we permit one if
455 // declared, but we default to 0 here (not -1 below)
456 // if there's no mention of a body.
460 // Body-EOF logic based on other methods (like closing, or chunked coding)
464 // Determine whether to hang up after sending a request and body, or
465 // receiving a response and body
466 // 'header' is the request headers
467 func shouldClose(major
, minor
int, header Header
) bool {
470 } else if major
== 1 && minor
== 0 {
471 if !strings
.Contains(strings
.ToLower(header
.get("Connection")), "keep-alive") {
476 // TODO: Should split on commas, toss surrounding white space,
477 // and check each field.
478 if strings
.ToLower(header
.get("Connection")) == "close" {
479 header
.Del("Connection")
486 // Parse the trailer header
487 func fixTrailer(header Header
, te
[]string) (Header
, error
) {
488 raw
:= header
.get("Trailer")
493 header
.Del("Trailer")
494 trailer
:= make(Header
)
495 keys
:= strings
.Split(raw
, ",")
496 for _
, key
:= range keys
{
497 key
= CanonicalHeaderKey(strings
.TrimSpace(key
))
499 case "Transfer-Encoding", "Trailer", "Content-Length":
500 return nil, &badStringError
{"bad trailer key", key
}
504 if len(trailer
) == 0 {
508 // Trailer and no chunking
509 return nil, ErrUnexpectedTrailer
514 // body turns a Reader into a ReadCloser.
515 // Close ensures that the body has been fully read
516 // and then reads the trailer if necessary.
519 hdr
interface{} // non-nil (Response or Request) value means read trailer
520 r
*bufio
.Reader
// underlying wire-format reader for the trailer
521 closing
bool // is the connection to be closed after reading body?
523 mu sync
.Mutex
// guards closed, and calls to Read and Close
527 // ErrBodyReadAfterClose is returned when reading a Request or Response
528 // Body after the body has been closed. This typically happens when the body is
529 // read after an HTTP Handler calls WriteHeader or Write on its
531 var ErrBodyReadAfterClose
= errors
.New("http: invalid Read on closed Body")
533 func (b
*body
) Read(p
[]byte) (n
int, err error
) {
537 return 0, ErrBodyReadAfterClose
539 return b
.readLocked(p
)
543 func (b
*body
) readLocked(p
[]byte) (n
int, err error
) {
544 n
, err
= b
.src
.Read(p
)
547 // Chunked case. Read the trailer.
549 if e
:= b
.readTrailer(); e
!= nil {
554 // If the server declared the Content-Length, our body is a LimitedReader
555 // and we need to check whether this EOF arrived early.
556 if lr
, ok
:= b
.src
.(*io
.LimitedReader
); ok
&& lr
.N
> 0 {
557 err
= io
.ErrUnexpectedEOF
566 singleCRLF
= []byte("\r\n")
567 doubleCRLF
= []byte("\r\n\r\n")
570 func seeUpcomingDoubleCRLF(r
*bufio
.Reader
) bool {
571 for peekSize
:= 4; ; peekSize
++ {
572 // This loop stops when Peek returns an error,
573 // which it does when r's buffer has been filled.
574 buf
, err
:= r
.Peek(peekSize
)
575 if bytes
.HasSuffix(buf
, doubleCRLF
) {
585 var errTrailerEOF
= errors
.New("http: unexpected EOF reading trailer")
587 func (b
*body
) readTrailer() error
{
588 // The common case, since nobody uses trailers.
589 buf
, err
:= b
.r
.Peek(2)
590 if bytes
.Equal(buf
, singleCRLF
) {
602 // Make sure there's a header terminator coming up, to prevent
603 // a DoS with an unbounded size Trailer. It's not easy to
604 // slip in a LimitReader here, as textproto.NewReader requires
605 // a concrete *bufio.Reader. Also, we can't get all the way
606 // back up to our conn's LimitedReader that *might* be backing
607 // this bufio.Reader. Instead, a hack: we iteratively Peek up
608 // to the bufio.Reader's max size, looking for a double CRLF.
609 // This limits the trailer to the underlying buffer size, typically 4kB.
610 if !seeUpcomingDoubleCRLF(b
.r
) {
611 return errors
.New("http: suspiciously long trailer after chunked body")
614 hdr
, err
:= textproto
.NewReader(b
.r
).ReadMIMEHeader()
621 switch rr
:= b
.hdr
.(type) {
623 rr
.Trailer
= Header(hdr
)
625 rr
.Trailer
= Header(hdr
)
630 func (b
*body
) Close() error
{
638 case b
.hdr
== nil && b
.closing
:
639 // no trailer and closing the connection next.
640 // no point in reading to EOF.
642 // Fully consume the body, which will also lead to us reading
643 // the trailer headers after the body, if present.
644 _
, err
= io
.Copy(ioutil
.Discard
, bodyLocked
{b
})
650 // bodyLocked is a io.Reader reading from a *body when its mutex is
652 type bodyLocked
struct {
656 func (bl bodyLocked
) Read(p
[]byte) (n
int, err error
) {
658 return 0, ErrBodyReadAfterClose
660 return bl
.b
.readLocked(p
)
663 // parseContentLength trims whitespace from s and returns -1 if no value
664 // is set, or the value if it's >= 0.
665 func parseContentLength(cl
string) (int64, error
) {
666 cl
= strings
.TrimSpace(cl
)
670 n
, err
:= strconv
.ParseInt(cl
, 10, 64)
671 if err
!= nil || n
< 0 {
672 return 0, &badStringError
{"bad Content-Length", cl
}