| // Copyright 2026 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 httpcommon | |
| import ( | |
| "bytes" | |
| "compress/gzip" | |
| "fmt" | |
| "io" | |
| "io/fs" | |
| "strings" | |
| "testing" | |
| "testing/synctest" | |
| ) | |
| // Tests that GzipReader doesn't crash on a second Read call following | |
| // the first Read call's gzip.NewReader returning an error. | |
| func TestGzipReaderDoubleReadCrash(t *testing.T) { | |
| gz := &GzipReader{Body: io.NopCloser(strings.NewReader("0123456789"))} | |
| var buf [1]byte | |
| n, err1 := gz.Read(buf[:]) | |
| if n != 0 || !strings.Contains(fmt.Sprint(err1), "invalid header") { | |
| t.Fatalf("Read = %v, %v; want 0, invalid header", n, err1) | |
| } | |
| n, err2 := gz.Read(buf[:]) | |
| if n != 0 || err2 != err1 { | |
| t.Fatalf("second Read = %v, %v; want 0, %v", n, err2, err1) | |
| } | |
| } | |
| func TestGzipReaderReadAfterClose(t *testing.T) { | |
| var body bytes.Buffer | |
| w := gzip.NewWriter(&body) | |
| w.Write([]byte("012345679")) | |
| w.Close() | |
| gz := &GzipReader{Body: io.NopCloser(&body)} | |
| var buf [1]byte | |
| n, err := gz.Read(buf[:]) | |
| if n != 1 || err != nil { | |
| t.Fatalf("first Read = %v, %v; want 1, nil", n, err) | |
| } | |
| if err := gz.Close(); err != nil { | |
| t.Fatalf("gz Close error: %v", err) | |
| } | |
| n, err = gz.Read(buf[:]) | |
| if n != 0 || err != fs.ErrClosed { | |
| t.Fatalf("Read after close = %v, %v; want 0, fs.ErrClosed", n, err) | |
| } | |
| } | |
| // blockingReadCloser blocks in Read until it is closed. | |
| type blockingReadCloser struct { | |
| closed chan struct{} | |
| } | |
| func (r *blockingReadCloser) Read([]byte) (int, error) { | |
| <-r.closed | |
| return 0, fs.ErrClosed | |
| } | |
| func (r *blockingReadCloser) Close() error { | |
| close(r.closed) | |
| return nil | |
| } | |
| // Tests that closing a GzipReader unblocks a Read that is waiting for the | |
| // gzip header, rather than deadlocking on the GzipReader's mutex. | |
| func TestGzipReaderConcurrentCloseAndRead(t *testing.T) { | |
| synctest.Test(t, func(t *testing.T) { | |
| gz := &GzipReader{Body: &blockingReadCloser{closed: make(chan struct{})}} | |
| readErr := make(chan error, 1) | |
| go func() { | |
| var buf [1]byte | |
| _, err := gz.Read(buf[:]) | |
| readErr <- err | |
| }() | |
| synctest.Wait() | |
| if err := gz.Close(); err != nil { | |
| t.Fatalf("Close = %v, want nil", err) | |
| } | |
| synctest.Wait() | |
| select { | |
| case err := <-readErr: | |
| if err == nil { | |
| t.Error("Read returned nil error, want error") | |
| } | |
| default: | |
| t.Fatal("Read did not unblock on Close") | |
| } | |
| }) | |
| } |