Skip to content

Commit 4d8031c

Browse files
committed
net/http: make the MaxBytesReader.Read error sticky
Fixes #14981 Change-Id: I39b906d119ca96815801a0fbef2dbe524a3246ff Reviewed-on: https://go-review.googlesource.com/23009 Reviewed-by: Andrew Gerrand <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 20e362d commit 4d8031c

File tree

2 files changed

+79
-51
lines changed

2 files changed

+79
-51
lines changed

src/net/http/request.go

Lines changed: 39 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -885,68 +885,56 @@ func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
885885
}
886886

887887
type maxBytesReader struct {
888-
w ResponseWriter
889-
r io.ReadCloser // underlying reader
890-
n int64 // max bytes remaining
891-
stopped bool
892-
sawEOF bool
888+
w ResponseWriter
889+
r io.ReadCloser // underlying reader
890+
n int64 // max bytes remaining
891+
err error // sticky error
893892
}
894893

895894
func (l *maxBytesReader) tooLarge() (n int, err error) {
896-
if !l.stopped {
897-
l.stopped = true
898-
899-
// The server code and client code both use
900-
// maxBytesReader. This "requestTooLarge" check is
901-
// only used by the server code. To prevent binaries
902-
// which only using the HTTP Client code (such as
903-
// cmd/go) from also linking in the HTTP server, don't
904-
// use a static type assertion to the server
905-
// "*response" type. Check this interface instead:
906-
type requestTooLarger interface {
907-
requestTooLarge()
908-
}
909-
if res, ok := l.w.(requestTooLarger); ok {
910-
res.requestTooLarge()
911-
}
912-
}
913-
return 0, errors.New("http: request body too large")
895+
l.err = errors.New("http: request body too large")
896+
return 0, l.err
914897
}
915898

916899
func (l *maxBytesReader) Read(p []byte) (n int, err error) {
917-
toRead := l.n
918-
if l.n == 0 {
919-
if l.sawEOF {
920-
return l.tooLarge()
921-
}
922-
// The underlying io.Reader may not return (0, io.EOF)
923-
// at EOF if the requested size is 0, so read 1 byte
924-
// instead. The io.Reader docs are a bit ambiguous
925-
// about the return value of Read when 0 bytes are
926-
// requested, and {bytes,strings}.Reader gets it wrong
927-
// too (it returns (0, nil) even at EOF).
928-
toRead = 1
900+
if l.err != nil {
901+
return 0, l.err
902+
}
903+
if len(p) == 0 {
904+
return 0, nil
929905
}
930-
if int64(len(p)) > toRead {
931-
p = p[:toRead]
906+
// If they asked for a 32KB byte read but only 5 bytes are
907+
// remaining, no need to read 32KB. 6 bytes will answer the
908+
// question of the whether we hit the limit or go past it.
909+
if int64(len(p)) > l.n+1 {
910+
p = p[:l.n+1]
932911
}
933912
n, err = l.r.Read(p)
934-
if err == io.EOF {
935-
l.sawEOF = true
936-
}
937-
if l.n == 0 {
938-
// If we had zero bytes to read remaining (but hadn't seen EOF)
939-
// and we get a byte here, that means we went over our limit.
940-
if n > 0 {
941-
return l.tooLarge()
942-
}
943-
return 0, err
913+
914+
if int64(n) <= l.n {
915+
l.n -= int64(n)
916+
l.err = err
917+
return n, err
944918
}
945-
l.n -= int64(n)
946-
if l.n < 0 {
947-
l.n = 0
919+
920+
n = int(l.n)
921+
l.n = 0
922+
923+
// The server code and client code both use
924+
// maxBytesReader. This "requestTooLarge" check is
925+
// only used by the server code. To prevent binaries
926+
// which only using the HTTP Client code (such as
927+
// cmd/go) from also linking in the HTTP server, don't
928+
// use a static type assertion to the server
929+
// "*response" type. Check this interface instead:
930+
type requestTooLarger interface {
931+
requestTooLarge()
948932
}
949-
return
933+
if res, ok := l.w.(requestTooLarger); ok {
934+
res.requestTooLarge()
935+
}
936+
l.err = errors.New("http: request body too large")
937+
return n, l.err
950938
}
951939

952940
func (l *maxBytesReader) Close() error {

src/net/http/request_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,46 @@ func TestIssue10884_MaxBytesEOF(t *testing.T) {
679679
}
680680
}
681681

682+
// Issue 14981: MaxBytesReader's return error wasn't sticky. It
683+
// doesn't technically need to be, but people expected it to be.
684+
func TestMaxBytesReaderStickyError(t *testing.T) {
685+
isSticky := func(r io.Reader) error {
686+
var log bytes.Buffer
687+
buf := make([]byte, 1000)
688+
var firstErr error
689+
for {
690+
n, err := r.Read(buf)
691+
fmt.Fprintf(&log, "Read(%d) = %d, %v\n", len(buf), n, err)
692+
if err == nil {
693+
continue
694+
}
695+
if firstErr == nil {
696+
firstErr = err
697+
continue
698+
}
699+
if !reflect.DeepEqual(err, firstErr) {
700+
return fmt.Errorf("non-sticky error. got log:\n%s", log.Bytes())
701+
}
702+
t.Logf("Got log: %s", log.Bytes())
703+
return nil
704+
}
705+
}
706+
tests := [...]struct {
707+
readable int
708+
limit int64
709+
}{
710+
0: {99, 100},
711+
1: {100, 100},
712+
2: {101, 100},
713+
}
714+
for i, tt := range tests {
715+
rc := MaxBytesReader(nil, ioutil.NopCloser(bytes.NewReader(make([]byte, tt.readable))), tt.limit)
716+
if err := isSticky(rc); err != nil {
717+
t.Errorf("%d. error: %v", i, err)
718+
}
719+
}
720+
}
721+
682722
func testMissingFile(t *testing.T, req *Request) {
683723
f, fh, err := req.FormFile("missing")
684724
if f != nil {

0 commit comments

Comments
 (0)