| // 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") |
| } |
| }) |
| } |