2010-02-19 09:38:40 -07:00
|
|
|
// Copyright 2009 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
2011-06-24 17:46:14 -06:00
|
|
|
"bytes"
|
2010-02-19 09:38:40 -07:00
|
|
|
"bufio"
|
|
|
|
"io"
|
2011-04-27 16:47:04 -06:00
|
|
|
"io/ioutil"
|
2010-02-19 09:38:40 -07:00
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// transferWriter inspects the fields of a user-supplied Request or Response,
|
|
|
|
// sanitizes them without changing the user object and provides methods for
|
|
|
|
// writing the respective header, body and trailer in wire format.
|
|
|
|
type transferWriter struct {
|
2011-06-24 17:46:14 -06:00
|
|
|
Body io.Reader
|
|
|
|
BodyCloser io.Closer
|
2010-02-19 09:38:40 -07:00
|
|
|
ResponseToHEAD bool
|
|
|
|
ContentLength int64
|
|
|
|
Close bool
|
|
|
|
TransferEncoding []string
|
2011-02-22 22:39:25 -07:00
|
|
|
Trailer Header
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func newTransferWriter(r interface{}) (t *transferWriter, err os.Error) {
|
|
|
|
t = &transferWriter{}
|
|
|
|
|
|
|
|
// Extract relevant fields
|
|
|
|
atLeastHTTP11 := false
|
|
|
|
switch rr := r.(type) {
|
|
|
|
case *Request:
|
|
|
|
t.Body = rr.Body
|
2011-06-24 17:46:14 -06:00
|
|
|
t.BodyCloser = rr.Body
|
2010-02-19 09:38:40 -07:00
|
|
|
t.ContentLength = rr.ContentLength
|
|
|
|
t.Close = rr.Close
|
|
|
|
t.TransferEncoding = rr.TransferEncoding
|
|
|
|
t.Trailer = rr.Trailer
|
|
|
|
atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
|
2011-06-24 17:46:14 -06:00
|
|
|
if t.Body != nil && len(t.TransferEncoding) == 0 && atLeastHTTP11 {
|
|
|
|
if t.ContentLength == 0 {
|
|
|
|
// Test to see if it's actually zero or just unset.
|
|
|
|
var buf [1]byte
|
|
|
|
n, _ := io.ReadFull(t.Body, buf[:])
|
|
|
|
if n == 1 {
|
|
|
|
// Oh, guess there is data in this Body Reader after all.
|
|
|
|
// The ContentLength field just wasn't set.
|
|
|
|
// Stich the Body back together again, re-attaching our
|
|
|
|
// consumed byte.
|
|
|
|
t.ContentLength = -1
|
|
|
|
t.Body = io.MultiReader(bytes.NewBuffer(buf[:]), t.Body)
|
|
|
|
} else {
|
|
|
|
// Body is actually empty.
|
|
|
|
t.Body = nil
|
|
|
|
t.BodyCloser = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if t.ContentLength < 0 {
|
|
|
|
t.TransferEncoding = []string{"chunked"}
|
|
|
|
}
|
http: fix handling of 0-lengthed http requests
Via Russ Ross' bug report on golang-nuts, it was not possible
to send an HTTP request with a zero length body with either a
Content-Length (it was stripped) or chunking (it wasn't set).
This means Go couldn't upload 0-length objects to Amazon S3.
(which aren't as silly as they might sound, as S3 objects can
have key/values associated with them, set in the headers)
Amazon further doesn't supported chunked uploads. (not Go's
problem, but we should be able to let users set an explicit
Content-Length, even if it's zero.)
To fix the ambiguity of an explicit zero Content-Length and
the Request struct's default zero value, users need to
explicit set TransferEncoding to []string{"identity"} to force
the Request.Write to include a Content-Length: 0. identity is
in RFC 2616 but is ignored pretty much everywhere. We don't
even then serialize it on the wire, since it's kinda useless,
except as an internal sentinel value.
The "identity" value is then documented, but most users can
ignore that because NewRequest now sets that.
And adds more tests.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/4603041
2011-06-08 16:59:23 -06:00
|
|
|
}
|
2010-02-19 09:38:40 -07:00
|
|
|
case *Response:
|
|
|
|
t.Body = rr.Body
|
2011-06-24 17:46:14 -06:00
|
|
|
t.BodyCloser = rr.Body
|
2010-02-19 09:38:40 -07:00
|
|
|
t.ContentLength = rr.ContentLength
|
|
|
|
t.Close = rr.Close
|
|
|
|
t.TransferEncoding = rr.TransferEncoding
|
|
|
|
t.Trailer = rr.Trailer
|
|
|
|
atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
|
2011-05-13 08:31:24 -06:00
|
|
|
t.ResponseToHEAD = noBodyExpected(rr.Request.Method)
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sanitize Body,ContentLength,TransferEncoding
|
|
|
|
if t.ResponseToHEAD {
|
|
|
|
t.Body = nil
|
|
|
|
t.TransferEncoding = nil
|
|
|
|
// ContentLength is expected to hold Content-Length
|
|
|
|
if t.ContentLength < 0 {
|
|
|
|
return nil, ErrMissingContentLength
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !atLeastHTTP11 || t.Body == nil {
|
|
|
|
t.TransferEncoding = nil
|
|
|
|
}
|
|
|
|
if chunked(t.TransferEncoding) {
|
|
|
|
t.ContentLength = -1
|
|
|
|
} else if t.Body == nil { // no chunking, no body
|
|
|
|
t.ContentLength = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sanitize Trailer
|
|
|
|
if !chunked(t.TransferEncoding) {
|
|
|
|
t.Trailer = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func noBodyExpected(requestMethod string) bool {
|
|
|
|
return requestMethod == "HEAD"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *transferWriter) WriteHeader(w io.Writer) (err os.Error) {
|
2010-02-24 16:13:39 -07:00
|
|
|
if t.Close {
|
|
|
|
_, err = io.WriteString(w, "Connection: close\r\n")
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-19 09:38:40 -07:00
|
|
|
// Write Content-Length and/or Transfer-Encoding whose values are a
|
|
|
|
// function of the sanitized field triple (Body, ContentLength,
|
|
|
|
// TransferEncoding)
|
|
|
|
if chunked(t.TransferEncoding) {
|
|
|
|
_, err = io.WriteString(w, "Transfer-Encoding: chunked\r\n")
|
2010-02-24 16:13:39 -07:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
http: fix handling of 0-lengthed http requests
Via Russ Ross' bug report on golang-nuts, it was not possible
to send an HTTP request with a zero length body with either a
Content-Length (it was stripped) or chunking (it wasn't set).
This means Go couldn't upload 0-length objects to Amazon S3.
(which aren't as silly as they might sound, as S3 objects can
have key/values associated with them, set in the headers)
Amazon further doesn't supported chunked uploads. (not Go's
problem, but we should be able to let users set an explicit
Content-Length, even if it's zero.)
To fix the ambiguity of an explicit zero Content-Length and
the Request struct's default zero value, users need to
explicit set TransferEncoding to []string{"identity"} to force
the Request.Write to include a Content-Length: 0. identity is
in RFC 2616 but is ignored pretty much everywhere. We don't
even then serialize it on the wire, since it's kinda useless,
except as an internal sentinel value.
The "identity" value is then documented, but most users can
ignore that because NewRequest now sets that.
And adds more tests.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/4603041
2011-06-08 16:59:23 -06:00
|
|
|
} else if t.ContentLength > 0 || t.ResponseToHEAD || (t.ContentLength == 0 && isIdentity(t.TransferEncoding)) {
|
2010-02-24 16:13:39 -07:00
|
|
|
io.WriteString(w, "Content-Length: ")
|
|
|
|
_, err = io.WriteString(w, strconv.Itoa64(t.ContentLength)+"\r\n")
|
|
|
|
if err != nil {
|
|
|
|
return
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write Trailer header
|
|
|
|
if t.Trailer != nil {
|
|
|
|
// TODO: At some point, there should be a generic mechanism for
|
|
|
|
// writing long headers, using HTTP line splitting
|
|
|
|
io.WriteString(w, "Trailer: ")
|
|
|
|
needComma := false
|
2010-12-08 22:36:56 -07:00
|
|
|
for k := range t.Trailer {
|
2010-02-19 09:38:40 -07:00
|
|
|
k = CanonicalHeaderKey(k)
|
|
|
|
switch k {
|
|
|
|
case "Transfer-Encoding", "Trailer", "Content-Length":
|
|
|
|
return &badStringError{"invalid Trailer key", k}
|
|
|
|
}
|
|
|
|
if needComma {
|
|
|
|
io.WriteString(w, ",")
|
|
|
|
}
|
|
|
|
io.WriteString(w, k)
|
|
|
|
needComma = true
|
|
|
|
}
|
|
|
|
_, err = io.WriteString(w, "\r\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *transferWriter) WriteBody(w io.Writer) (err os.Error) {
|
|
|
|
// Write body
|
|
|
|
if t.Body != nil {
|
|
|
|
if chunked(t.TransferEncoding) {
|
|
|
|
cw := NewChunkedWriter(w)
|
|
|
|
_, err = io.Copy(cw, t.Body)
|
|
|
|
if err == nil {
|
|
|
|
err = cw.Close()
|
|
|
|
}
|
2010-07-18 22:05:27 -06:00
|
|
|
} else if t.ContentLength == -1 {
|
|
|
|
_, err = io.Copy(w, t.Body)
|
2010-02-19 09:38:40 -07:00
|
|
|
} else {
|
|
|
|
_, err = io.Copy(w, io.LimitReader(t.Body, t.ContentLength))
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2011-06-24 17:46:14 -06:00
|
|
|
if err = t.BodyCloser.Close(); err != nil {
|
2010-02-19 09:38:40 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(petar): Place trailer writer code here.
|
|
|
|
if chunked(t.TransferEncoding) {
|
|
|
|
// Last chunk, empty trailer
|
|
|
|
_, err = io.WriteString(w, "\r\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type transferReader struct {
|
|
|
|
// Input
|
2011-02-22 22:39:25 -07:00
|
|
|
Header Header
|
2010-02-19 09:38:40 -07:00
|
|
|
StatusCode int
|
|
|
|
RequestMethod string
|
|
|
|
ProtoMajor int
|
|
|
|
ProtoMinor int
|
|
|
|
// Output
|
|
|
|
Body io.ReadCloser
|
|
|
|
ContentLength int64
|
|
|
|
TransferEncoding []string
|
|
|
|
Close bool
|
2011-02-22 22:39:25 -07:00
|
|
|
Trailer Header
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
|
|
|
|
2011-02-08 21:35:02 -07:00
|
|
|
// bodyAllowedForStatus returns whether a given response status code
|
|
|
|
// permits a body. See RFC2616, section 4.4.
|
|
|
|
func bodyAllowedForStatus(status int) bool {
|
|
|
|
switch {
|
|
|
|
case status >= 100 && status <= 199:
|
|
|
|
return false
|
|
|
|
case status == 204:
|
|
|
|
return false
|
|
|
|
case status == 304:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2010-02-19 09:38:40 -07:00
|
|
|
// msg is *Request or *Response.
|
|
|
|
func readTransfer(msg interface{}, r *bufio.Reader) (err os.Error) {
|
|
|
|
t := &transferReader{}
|
|
|
|
|
|
|
|
// Unify input
|
2011-06-24 14:48:12 -06:00
|
|
|
isResponse := false
|
2010-02-19 09:38:40 -07:00
|
|
|
switch rr := msg.(type) {
|
|
|
|
case *Response:
|
|
|
|
t.Header = rr.Header
|
|
|
|
t.StatusCode = rr.StatusCode
|
2011-05-13 08:31:24 -06:00
|
|
|
t.RequestMethod = rr.Request.Method
|
2010-02-19 09:38:40 -07:00
|
|
|
t.ProtoMajor = rr.ProtoMajor
|
|
|
|
t.ProtoMinor = rr.ProtoMinor
|
2010-10-19 21:29:25 -06:00
|
|
|
t.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header)
|
2011-06-24 14:48:12 -06:00
|
|
|
isResponse = true
|
2010-02-19 09:38:40 -07:00
|
|
|
case *Request:
|
|
|
|
t.Header = rr.Header
|
|
|
|
t.ProtoMajor = rr.ProtoMajor
|
|
|
|
t.ProtoMinor = rr.ProtoMinor
|
|
|
|
// Transfer semantics for Requests are exactly like those for
|
|
|
|
// Responses with status code 200, responding to a GET method
|
|
|
|
t.StatusCode = 200
|
|
|
|
t.RequestMethod = "GET"
|
2011-06-24 14:48:12 -06:00
|
|
|
default:
|
|
|
|
panic("unexpected type")
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
|
|
|
|
2010-02-24 16:13:39 -07:00
|
|
|
// Default to HTTP/1.1
|
|
|
|
if t.ProtoMajor == 0 && t.ProtoMinor == 0 {
|
|
|
|
t.ProtoMajor, t.ProtoMinor = 1, 1
|
|
|
|
}
|
|
|
|
|
2010-02-19 09:38:40 -07:00
|
|
|
// Transfer encoding, content length
|
2011-04-04 20:43:36 -06:00
|
|
|
t.TransferEncoding, err = fixTransferEncoding(t.RequestMethod, t.Header)
|
2010-02-19 09:38:40 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2011-06-24 14:48:12 -06:00
|
|
|
t.ContentLength, err = fixLength(isResponse, t.StatusCode, t.RequestMethod, t.Header, t.TransferEncoding)
|
2010-02-19 09:38:40 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trailer
|
|
|
|
t.Trailer, err = fixTrailer(t.Header, t.TransferEncoding)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2011-02-08 21:35:02 -07:00
|
|
|
// If there is no Content-Length or chunked Transfer-Encoding on a *Response
|
|
|
|
// and the status is not 1xx, 204 or 304, then the body is unbounded.
|
|
|
|
// See RFC2616, section 4.4.
|
|
|
|
switch msg.(type) {
|
|
|
|
case *Response:
|
|
|
|
if t.ContentLength == -1 &&
|
|
|
|
!chunked(t.TransferEncoding) &&
|
|
|
|
bodyAllowedForStatus(t.StatusCode) {
|
|
|
|
// Unbounded body.
|
|
|
|
t.Close = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-19 09:38:40 -07:00
|
|
|
// Prepare body reader. ContentLength < 0 means chunked encoding
|
|
|
|
// or close connection when finished, since multipart is not supported yet
|
|
|
|
switch {
|
|
|
|
case chunked(t.TransferEncoding):
|
2011-06-29 13:27:53 -06:00
|
|
|
t.Body = &body{Reader: NewChunkedReader(r), hdr: msg, r: r, closing: t.Close}
|
2010-02-19 09:38:40 -07:00
|
|
|
case t.ContentLength >= 0:
|
|
|
|
// TODO: limit the Content-Length. This is an easy DoS vector.
|
|
|
|
t.Body = &body{Reader: io.LimitReader(r, t.ContentLength), closing: t.Close}
|
|
|
|
default:
|
|
|
|
// t.ContentLength < 0, i.e. "Content-Length" not mentioned in header
|
|
|
|
if t.Close {
|
|
|
|
// Close semantics (i.e. HTTP/1.0)
|
|
|
|
t.Body = &body{Reader: r, closing: t.Close}
|
|
|
|
} else {
|
|
|
|
// Persistent connection (i.e. HTTP/1.1)
|
|
|
|
t.Body = &body{Reader: io.LimitReader(r, 0), closing: t.Close}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unify output
|
|
|
|
switch rr := msg.(type) {
|
|
|
|
case *Request:
|
|
|
|
rr.Body = t.Body
|
|
|
|
rr.ContentLength = t.ContentLength
|
|
|
|
rr.TransferEncoding = t.TransferEncoding
|
|
|
|
rr.Close = t.Close
|
|
|
|
rr.Trailer = t.Trailer
|
|
|
|
case *Response:
|
|
|
|
rr.Body = t.Body
|
|
|
|
rr.ContentLength = t.ContentLength
|
|
|
|
rr.TransferEncoding = t.TransferEncoding
|
|
|
|
rr.Close = t.Close
|
|
|
|
rr.Trailer = t.Trailer
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks whether chunked is part of the encodings stack
|
|
|
|
func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
|
|
|
|
|
http: fix handling of 0-lengthed http requests
Via Russ Ross' bug report on golang-nuts, it was not possible
to send an HTTP request with a zero length body with either a
Content-Length (it was stripped) or chunking (it wasn't set).
This means Go couldn't upload 0-length objects to Amazon S3.
(which aren't as silly as they might sound, as S3 objects can
have key/values associated with them, set in the headers)
Amazon further doesn't supported chunked uploads. (not Go's
problem, but we should be able to let users set an explicit
Content-Length, even if it's zero.)
To fix the ambiguity of an explicit zero Content-Length and
the Request struct's default zero value, users need to
explicit set TransferEncoding to []string{"identity"} to force
the Request.Write to include a Content-Length: 0. identity is
in RFC 2616 but is ignored pretty much everywhere. We don't
even then serialize it on the wire, since it's kinda useless,
except as an internal sentinel value.
The "identity" value is then documented, but most users can
ignore that because NewRequest now sets that.
And adds more tests.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/4603041
2011-06-08 16:59:23 -06:00
|
|
|
// Checks whether the encoding is explicitly "identity".
|
|
|
|
func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" }
|
|
|
|
|
2010-02-19 09:38:40 -07:00
|
|
|
// Sanitize transfer encoding
|
2011-04-04 20:43:36 -06:00
|
|
|
func fixTransferEncoding(requestMethod string, header Header) ([]string, os.Error) {
|
2010-02-19 09:38:40 -07:00
|
|
|
raw, present := header["Transfer-Encoding"]
|
|
|
|
if !present {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2011-02-22 22:39:25 -07:00
|
|
|
header["Transfer-Encoding"] = nil, false
|
2011-04-04 20:43:36 -06:00
|
|
|
|
|
|
|
// Head responses have no bodies, so the transfer encoding
|
|
|
|
// should be ignored.
|
|
|
|
if requestMethod == "HEAD" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2011-06-27 17:43:14 -06:00
|
|
|
encodings := strings.Split(raw[0], ",")
|
2010-02-19 09:38:40 -07:00
|
|
|
te := make([]string, 0, len(encodings))
|
|
|
|
// TODO: Even though we only support "identity" and "chunked"
|
|
|
|
// encodings, the loop below is designed with foresight. One
|
|
|
|
// invariant that must be maintained is that, if present,
|
|
|
|
// chunked encoding must always come first.
|
|
|
|
for _, encoding := range encodings {
|
|
|
|
encoding = strings.ToLower(strings.TrimSpace(encoding))
|
|
|
|
// "identity" encoding is not recored
|
|
|
|
if encoding == "identity" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if encoding != "chunked" {
|
|
|
|
return nil, &badStringError{"unsupported transfer encoding", encoding}
|
|
|
|
}
|
|
|
|
te = te[0 : len(te)+1]
|
|
|
|
te[len(te)-1] = encoding
|
|
|
|
}
|
|
|
|
if len(te) > 1 {
|
|
|
|
return nil, &badStringError{"too many transfer encodings", strings.Join(te, ",")}
|
|
|
|
}
|
|
|
|
if len(te) > 0 {
|
|
|
|
// Chunked encoding trumps Content-Length. See RFC 2616
|
|
|
|
// Section 4.4. Currently len(te) > 0 implies chunked
|
|
|
|
// encoding.
|
2011-02-22 22:39:25 -07:00
|
|
|
header["Content-Length"] = nil, false
|
2010-02-19 09:38:40 -07:00
|
|
|
return te, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the expected body length, using RFC 2616 Section 4.4. This
|
|
|
|
// function is not a method, because ultimately it should be shared by
|
|
|
|
// ReadResponse and ReadRequest.
|
2011-06-24 14:48:12 -06:00
|
|
|
func fixLength(isResponse bool, status int, requestMethod string, header Header, te []string) (int64, os.Error) {
|
2010-02-19 09:38:40 -07:00
|
|
|
|
|
|
|
// Logic based on response type or status
|
|
|
|
if noBodyExpected(requestMethod) {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
if status/100 == 1 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
switch status {
|
|
|
|
case 204, 304:
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Logic based on Transfer-Encoding
|
|
|
|
if chunked(te) {
|
|
|
|
return -1, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Logic based on Content-Length
|
2011-02-22 22:39:25 -07:00
|
|
|
cl := strings.TrimSpace(header.Get("Content-Length"))
|
|
|
|
if cl != "" {
|
|
|
|
n, err := strconv.Atoi64(cl)
|
|
|
|
if err != nil || n < 0 {
|
|
|
|
return -1, &badStringError{"bad Content-Length", cl}
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
2011-02-22 22:39:25 -07:00
|
|
|
return n, nil
|
|
|
|
} else {
|
|
|
|
header.Del("Content-Length")
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
|
|
|
|
2011-06-24 14:48:12 -06:00
|
|
|
if !isResponse && requestMethod == "GET" {
|
|
|
|
// RFC 2616 doesn't explicitly permit nor forbid an
|
|
|
|
// entity-body on a GET request so we permit one if
|
|
|
|
// declared, but we default to 0 here (not -1 below)
|
|
|
|
// if there's no mention of a body.
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
2010-02-19 09:38:40 -07:00
|
|
|
// Logic based on media type. The purpose of the following code is just
|
|
|
|
// to detect whether the unsupported "multipart/byteranges" is being
|
|
|
|
// used. A proper Content-Type parser is needed in the future.
|
2011-02-22 22:39:25 -07:00
|
|
|
if strings.Contains(strings.ToLower(header.Get("Content-Type")), "multipart/byteranges") {
|
2010-03-30 11:51:11 -06:00
|
|
|
return -1, ErrNotSupported
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Body-EOF logic based on other methods (like closing, or chunked coding)
|
|
|
|
return -1, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine whether to hang up after sending a request and body, or
|
|
|
|
// receiving a response and body
|
2010-09-27 19:55:04 -06:00
|
|
|
// 'header' is the request headers
|
2011-02-22 22:39:25 -07:00
|
|
|
func shouldClose(major, minor int, header Header) bool {
|
2010-09-27 19:55:04 -06:00
|
|
|
if major < 1 {
|
2010-02-19 09:38:40 -07:00
|
|
|
return true
|
2010-09-27 19:55:04 -06:00
|
|
|
} else if major == 1 && minor == 0 {
|
2011-02-22 22:39:25 -07:00
|
|
|
if !strings.Contains(strings.ToLower(header.Get("Connection")), "keep-alive") {
|
2010-09-27 19:55:04 -06:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
2011-02-22 22:39:25 -07:00
|
|
|
} else {
|
2010-02-19 09:38:40 -07:00
|
|
|
// TODO: Should split on commas, toss surrounding white space,
|
|
|
|
// and check each field.
|
2011-02-22 22:39:25 -07:00
|
|
|
if strings.ToLower(header.Get("Connection")) == "close" {
|
|
|
|
header.Del("Connection")
|
2010-02-19 09:38:40 -07:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the trailer header
|
2011-02-22 22:39:25 -07:00
|
|
|
func fixTrailer(header Header, te []string) (Header, os.Error) {
|
|
|
|
raw := header.Get("Trailer")
|
|
|
|
if raw == "" {
|
2010-02-19 09:38:40 -07:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2011-02-22 22:39:25 -07:00
|
|
|
header.Del("Trailer")
|
|
|
|
trailer := make(Header)
|
2011-06-27 17:43:14 -06:00
|
|
|
keys := strings.Split(raw, ",")
|
2010-02-19 09:38:40 -07:00
|
|
|
for _, key := range keys {
|
|
|
|
key = CanonicalHeaderKey(strings.TrimSpace(key))
|
|
|
|
switch key {
|
|
|
|
case "Transfer-Encoding", "Trailer", "Content-Length":
|
|
|
|
return nil, &badStringError{"bad trailer key", key}
|
|
|
|
}
|
2011-02-22 22:39:25 -07:00
|
|
|
trailer.Del(key)
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
|
|
|
if len(trailer) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if !chunked(te) {
|
|
|
|
// Trailer and no chunking
|
|
|
|
return nil, ErrUnexpectedTrailer
|
|
|
|
}
|
|
|
|
return trailer, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// body turns a Reader into a ReadCloser.
|
|
|
|
// Close ensures that the body has been fully read
|
|
|
|
// and then reads the trailer if necessary.
|
|
|
|
type body struct {
|
|
|
|
io.Reader
|
|
|
|
hdr interface{} // non-nil (Response or Request) value means read trailer
|
|
|
|
r *bufio.Reader // underlying wire-format reader for the trailer
|
|
|
|
closing bool // is the connection to be closed after reading body?
|
2011-04-30 20:54:08 -06:00
|
|
|
closed bool
|
2011-08-23 02:17:21 -06:00
|
|
|
|
|
|
|
res *response // response writer for server requests, else nil
|
2011-04-30 20:54:08 -06:00
|
|
|
}
|
|
|
|
|
2011-05-01 13:37:20 -06:00
|
|
|
// ErrBodyReadAfterClose is returned when reading a Request Body after
|
2011-04-30 20:54:08 -06:00
|
|
|
// the body has been closed. This typically happens when the body is
|
|
|
|
// read after an HTTP Handler calls WriteHeader or Write on its
|
|
|
|
// ResponseWriter.
|
2011-05-01 13:37:20 -06:00
|
|
|
var ErrBodyReadAfterClose = os.NewError("http: invalid Read on closed request Body")
|
2011-04-30 20:54:08 -06:00
|
|
|
|
|
|
|
func (b *body) Read(p []byte) (n int, err os.Error) {
|
|
|
|
if b.closed {
|
2011-05-01 13:37:20 -06:00
|
|
|
return 0, ErrBodyReadAfterClose
|
2011-04-30 20:54:08 -06:00
|
|
|
}
|
|
|
|
return b.Reader.Read(p)
|
2010-02-19 09:38:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *body) Close() os.Error {
|
2011-04-30 20:54:08 -06:00
|
|
|
if b.closed {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
b.closed = true
|
|
|
|
}()
|
2010-02-19 09:38:40 -07:00
|
|
|
if b.hdr == nil && b.closing {
|
|
|
|
// no trailer and closing the connection next.
|
|
|
|
// no point in reading to EOF.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-08-23 02:17:21 -06:00
|
|
|
// In a server request, don't continue reading from the client
|
|
|
|
// if we've already hit the maximum body size set by the
|
|
|
|
// handler. If this is set, that also means the TCP connection
|
|
|
|
// is about to be closed, so getting to the next HTTP request
|
|
|
|
// in the stream is not necessary.
|
|
|
|
if b.res != nil && b.res.requestBodyLimitHit {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-04-27 16:47:04 -06:00
|
|
|
if _, err := io.Copy(ioutil.Discard, b); err != nil {
|
2010-02-19 09:38:40 -07:00
|
|
|
return err
|
|
|
|
}
|
2011-04-27 16:47:04 -06:00
|
|
|
|
2010-02-19 09:38:40 -07:00
|
|
|
if b.hdr == nil { // not reading trailer
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(petar): Put trailer reader code here
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|