diff options
Diffstat (limited to 'src/pkg/net/http/response.go')
-rw-r--r-- | src/pkg/net/http/response.go | 68 |
1 files changed, 63 insertions, 5 deletions
diff --git a/src/pkg/net/http/response.go b/src/pkg/net/http/response.go index 35d0ba3bb..5d2c39080 100644 --- a/src/pkg/net/http/response.go +++ b/src/pkg/net/http/response.go @@ -8,6 +8,8 @@ package http import ( "bufio" + "bytes" + "crypto/tls" "errors" "io" "net/textproto" @@ -45,7 +47,8 @@ type Response struct { // // The http Client and Transport guarantee that Body is always // non-nil, even on responses without a body or responses with - // a zero-lengthed body. + // a zero-length body. It is the caller's responsibility to + // close Body. // // The Body is automatically dechunked if the server replied // with a "chunked" Transfer-Encoding. @@ -74,6 +77,12 @@ type Response struct { // Request's Body is nil (having already been consumed). // This is only populated for Client requests. Request *Request + + // TLS contains information about the TLS connection on which the + // response was received. It is nil for unencrypted responses. + // The pointer is shared between responses and should not be + // modified. + TLS *tls.ConnectionState } // Cookies parses and returns the cookies set in the Set-Cookie headers. @@ -141,6 +150,9 @@ func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) { // Parse the response headers. mimeHeader, err := tp.ReadMIMEHeader() if err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } return nil, err } resp.Header = Header(mimeHeader) @@ -187,8 +199,8 @@ func (r *Response) ProtoAtLeast(major, minor int) bool { // ContentLength // Header, values for non-canonical keys will have unpredictable behavior // +// Body is closed after it is sent. func (r *Response) Write(w io.Writer) error { - // Status line text := r.Status if text == "" { @@ -201,10 +213,45 @@ func (r *Response) Write(w io.Writer) error { protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor) statusCode := strconv.Itoa(r.StatusCode) + " " text = strings.TrimPrefix(text, statusCode) - io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n") + if _, err := io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n"); err != nil { + return err + } + + // Clone it, so we can modify r1 as needed. + r1 := new(Response) + *r1 = *r + if r1.ContentLength == 0 && r1.Body != nil { + // Is it actually 0 length? Or just unknown? + var buf [1]byte + n, err := r1.Body.Read(buf[:]) + if err != nil && err != io.EOF { + return err + } + if n == 0 { + // Reset it to a known zero reader, in case underlying one + // is unhappy being read repeatedly. + r1.Body = eofReader + } else { + r1.ContentLength = -1 + r1.Body = struct { + io.Reader + io.Closer + }{ + io.MultiReader(bytes.NewReader(buf[:1]), r.Body), + r.Body, + } + } + } + // If we're sending a non-chunked HTTP/1.1 response without a + // content-length, the only way to do that is the old HTTP/1.0 + // way, by noting the EOF with a connection close, so we need + // to set Close. + if r1.ContentLength == -1 && !r1.Close && r1.ProtoAtLeast(1, 1) && !chunked(r1.TransferEncoding) { + r1.Close = true + } // Process Body,ContentLength,Close,Trailer - tw, err := newTransferWriter(r) + tw, err := newTransferWriter(r1) if err != nil { return err } @@ -219,8 +266,19 @@ func (r *Response) Write(w io.Writer) error { return err } + // contentLengthAlreadySent may have been already sent for + // POST/PUT requests, even if zero length. See Issue 8180. + contentLengthAlreadySent := tw.shouldSendContentLength() + if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent { + if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil { + return err + } + } + // End-of-header - io.WriteString(w, "\r\n") + if _, err := io.WriteString(w, "\r\n"); err != nil { + return err + } // Write body and trailer err = tw.WriteBody(w) |