Commit 2a441d30 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http/internal: don't block unnecessarily in ChunkedReader

Fixes #17355

Change-Id: I5390979cd0081b61a639466377faa46b4221b74a
Reviewed-on: https://go-review.googlesource.com/31329
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent 430b8200
...@@ -35,10 +35,11 @@ func NewChunkedReader(r io.Reader) io.Reader { ...@@ -35,10 +35,11 @@ func NewChunkedReader(r io.Reader) io.Reader {
} }
type chunkedReader struct { type chunkedReader struct {
r *bufio.Reader r *bufio.Reader
n uint64 // unread bytes in chunk n uint64 // unread bytes in chunk
err error err error
buf [2]byte buf [2]byte
checkEnd bool // whether need to check for \r\n chunk footer
} }
func (cr *chunkedReader) beginChunk() { func (cr *chunkedReader) beginChunk() {
...@@ -68,6 +69,21 @@ func (cr *chunkedReader) chunkHeaderAvailable() bool { ...@@ -68,6 +69,21 @@ func (cr *chunkedReader) chunkHeaderAvailable() bool {
func (cr *chunkedReader) Read(b []uint8) (n int, err error) { func (cr *chunkedReader) Read(b []uint8) (n int, err error) {
for cr.err == nil { for cr.err == nil {
if cr.checkEnd {
if n > 0 && cr.r.Buffered() < 2 {
// We have some data. Return early (per the io.Reader
// contract) instead of potentially blocking while
// reading more.
break
}
if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err == nil {
if string(cr.buf[:]) != "\r\n" {
cr.err = errors.New("malformed chunked encoding")
break
}
}
cr.checkEnd = false
}
if cr.n == 0 { if cr.n == 0 {
if n > 0 && !cr.chunkHeaderAvailable() { if n > 0 && !cr.chunkHeaderAvailable() {
// We've read enough. Don't potentially block // We've read enough. Don't potentially block
...@@ -92,11 +108,7 @@ func (cr *chunkedReader) Read(b []uint8) (n int, err error) { ...@@ -92,11 +108,7 @@ func (cr *chunkedReader) Read(b []uint8) (n int, err error) {
// If we're at the end of a chunk, read the next two // If we're at the end of a chunk, read the next two
// bytes to verify they are "\r\n". // bytes to verify they are "\r\n".
if cr.n == 0 && cr.err == nil { if cr.n == 0 && cr.err == nil {
if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err == nil { cr.checkEnd = true
if cr.buf[0] != '\r' || cr.buf[1] != '\n' {
cr.err = errors.New("malformed chunked encoding")
}
}
} }
} }
return n, cr.err return n, cr.err
......
...@@ -185,3 +185,30 @@ func TestChunkReadingIgnoresExtensions(t *testing.T) { ...@@ -185,3 +185,30 @@ func TestChunkReadingIgnoresExtensions(t *testing.T) {
t.Errorf("read %q; want %q", g, e) t.Errorf("read %q; want %q", g, e)
} }
} }
// Issue 17355: ChunkedReader shouldn't block waiting for more data
// if it can return something.
func TestChunkReadPartial(t *testing.T) {
pr, pw := io.Pipe()
go func() {
pw.Write([]byte("7\r\n1234567"))
}()
cr := NewChunkedReader(pr)
readBuf := make([]byte, 7)
n, err := cr.Read(readBuf)
if err != nil {
t.Fatal(err)
}
want := "1234567"
if n != 7 || string(readBuf) != want {
t.Fatalf("Read: %v %q; want %d, %q", n, readBuf[:n], len(want), want)
}
go func() {
pw.Write([]byte("xx"))
}()
_, err = cr.Read(readBuf)
if got := fmt.Sprint(err); !strings.Contains(got, "malformed") {
t.Fatalf("second read = %v; want malformed error", err)
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment