Skip to content

Commit f9fe783

Browse files
committed
Revert "net/http: support gzip, x-gzip Transfer-Encodings"
This reverts commit e6c12c3. Reason for revert: the assumption that a T-E of "gzip" implies "chunked" seems incorrect. The RFC does state that one "MUST apply chunked as the final transfer coding" but that should be interpreted to mean that a "chunked" encoding must be listed as the last one, not that one should be assumed to be there if not. This is confirmed by the alternative option to chunking on the server side being to "terminate the message by closing the connection". The issue seems confirmed by the fact that the code in the body of #29162 fails with the following error: net/http: HTTP/1.x transport connection broken: http: failed to gunzip body: unexpected EOF This late in the cycle, revert rather than fix, also because we don't apparently have tests for the correct behavior. Change-Id: I920ec928754cd8e96a06fb7ff8a53316c0f959e5 Reviewed-on: https://go-review.googlesource.com/c/go/+/215757 Run-TryBot: Filippo Valsorda <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Katie Hockman <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]>
1 parent 9b5bd30 commit f9fe783

File tree

2 files changed

+15
-394
lines changed

2 files changed

+15
-394
lines changed

src/net/http/transfer.go

Lines changed: 13 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package http
77
import (
88
"bufio"
99
"bytes"
10-
"compress/gzip"
1110
"errors"
1211
"fmt"
1312
"io"
@@ -467,34 +466,6 @@ func suppressedHeaders(status int) []string {
467466
return nil
468467
}
469468

470-
// proxyingReadCloser is a composite type that accepts and proxies
471-
// io.Read and io.Close calls to its respective Reader and Closer.
472-
//
473-
// It is composed of:
474-
// a) a top-level reader e.g. the result of decompression
475-
// b) a symbolic Closer e.g. the result of decompression, the
476-
// original body and the connection itself.
477-
type proxyingReadCloser struct {
478-
io.Reader
479-
io.Closer
480-
}
481-
482-
// multiCloser implements io.Closer and allows a bunch of io.Closer values
483-
// to all be closed once.
484-
// Example usage is with proxyingReadCloser if we are decompressing a response
485-
// body on the fly and would like to close both *gzip.Reader and underlying body.
486-
type multiCloser []io.Closer
487-
488-
func (mc multiCloser) Close() error {
489-
var err error
490-
for _, c := range mc {
491-
if err1 := c.Close(); err1 != nil && err == nil {
492-
err = err1
493-
}
494-
}
495-
return err
496-
}
497-
498469
// msg is *Request or *Response.
499470
func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
500471
t := &transferReader{RequestMethod: "GET"}
@@ -572,7 +543,7 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
572543
// Prepare body reader. ContentLength < 0 means chunked encoding
573544
// or close connection when finished, since multipart is not supported yet
574545
switch {
575-
case chunked(t.TransferEncoding) || implicitlyChunked(t.TransferEncoding):
546+
case chunked(t.TransferEncoding):
576547
if noResponseBodyExpected(t.RequestMethod) || !bodyAllowedForStatus(t.StatusCode) {
577548
t.Body = NoBody
578549
} else {
@@ -593,21 +564,6 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
593564
}
594565
}
595566

596-
// Finally if "gzip" was one of the requested transfer-encodings,
597-
// we'll unzip the concatenated body/payload of the request.
598-
// TODO: As we support more transfer-encodings, extract
599-
// this code and apply the un-codings in reverse.
600-
if t.Body != NoBody && gzipped(t.TransferEncoding) {
601-
zr, err := gzip.NewReader(t.Body)
602-
if err != nil {
603-
return fmt.Errorf("http: failed to gunzip body: %v", err)
604-
}
605-
t.Body = &proxyingReadCloser{
606-
Reader: zr,
607-
Closer: multiCloser{zr, t.Body},
608-
}
609-
}
610-
611567
// Unify output
612568
switch rr := msg.(type) {
613569
case *Request:
@@ -627,41 +583,8 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
627583
return nil
628584
}
629585

630-
// Checks whether chunked is the last part of the encodings stack
631-
func chunked(te []string) bool { return len(te) > 0 && te[len(te)-1] == "chunked" }
632-
633-
// implicitlyChunked is a helper to check for implicity of chunked, because
634-
// RFC 7230 Section 3.3.1 says that the sender MUST apply chunked as the final
635-
// payload body to ensure that the message is framed for both the request
636-
// and the body. Since "identity" is incompatible with any other transformational
637-
// encoding cannot co-exist, the presence of "identity" will cause implicitlyChunked
638-
// to return false.
639-
func implicitlyChunked(te []string) bool {
640-
if len(te) == 0 { // No transfer-encodings passed in, so not implicitly chunked.
641-
return false
642-
}
643-
for _, tei := range te {
644-
if tei == "identity" {
645-
return false
646-
}
647-
}
648-
return true
649-
}
650-
651-
func isGzipTransferEncoding(tei string) bool {
652-
// RFC 7230 4.2.3 requests that "x-gzip" SHOULD be considered the same as "gzip".
653-
return tei == "gzip" || tei == "x-gzip"
654-
}
655-
656-
// Checks where either of "gzip" or "x-gzip" are contained in transfer encodings.
657-
func gzipped(te []string) bool {
658-
for _, tei := range te {
659-
if isGzipTransferEncoding(tei) {
660-
return true
661-
}
662-
}
663-
return false
664-
}
586+
// Checks whether chunked is part of the encodings stack
587+
func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
665588

666589
// Checks whether the encoding is explicitly "identity".
667590
func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" }
@@ -697,47 +620,25 @@ func (t *transferReader) fixTransferEncoding() error {
697620

698621
encodings := strings.Split(raw[0], ",")
699622
te := make([]string, 0, len(encodings))
700-
701-
// When adding new encodings, please maintain the invariant:
702-
// if chunked encoding is present, it must always
703-
// come last and it must be applied only once.
704-
// See RFC 7230 Section 3.3.1 Transfer-Encoding.
705-
for i, encoding := range encodings {
623+
// TODO: Even though we only support "identity" and "chunked"
624+
// encodings, the loop below is designed with foresight. One
625+
// invariant that must be maintained is that, if present,
626+
// chunked encoding must always come first.
627+
for _, encoding := range encodings {
706628
encoding = strings.ToLower(strings.TrimSpace(encoding))
707-
629+
// "identity" encoding is not recorded
708630
if encoding == "identity" {
709-
// "identity" should not be mixed with other transfer-encodings/compressions
710-
// because it means "no compression, no transformation".
711-
if len(encodings) != 1 {
712-
return &badStringError{`"identity" when present must be the only transfer encoding`, strings.Join(encodings, ",")}
713-
}
714-
// "identity" is not recorded.
715631
break
716632
}
717-
718-
switch {
719-
case encoding == "chunked":
720-
// "chunked" MUST ALWAYS be the last
721-
// encoding as per the loop invariant.
722-
// That is:
723-
// Invalid: [chunked, gzip]
724-
// Valid: [gzip, chunked]
725-
if i+1 != len(encodings) {
726-
return &badStringError{"chunked must be applied only once, as the last encoding", strings.Join(encodings, ",")}
727-
}
728-
// Supported otherwise.
729-
730-
case isGzipTransferEncoding(encoding):
731-
// Supported
732-
733-
default:
633+
if encoding != "chunked" {
734634
return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", encoding)}
735635
}
736-
737636
te = te[0 : len(te)+1]
738637
te[len(te)-1] = encoding
739638
}
740-
639+
if len(te) > 1 {
640+
return &badStringError{"too many transfer encodings", strings.Join(te, ",")}
641+
}
741642
if len(te) > 0 {
742643
// RFC 7230 3.3.2 says "A sender MUST NOT send a
743644
// Content-Length header field in any message that

0 commit comments

Comments
 (0)