From e16930e4aa30f1f48be92d8d0f57073d8268fd43 Mon Sep 17 00:00:00 2001 From: Lev Date: Sat, 11 Apr 2026 16:45:24 +0300 Subject: [PATCH 1/2] fix: use safe type assertions in Hijack and CloseNotify to prevent panics --- response_writer.go | 18 ++++++++++++--- response_writer_test.go | 51 +++++++++++++++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/response_writer.go b/response_writer.go index 9035e6f1fd..305322b63e 100644 --- a/response_writer.go +++ b/response_writer.go @@ -17,7 +17,10 @@ const ( defaultStatus = http.StatusOK ) -var errHijackAlreadyWritten = errors.New("gin: response body already written") +var ( + errHijackAlreadyWritten = errors.New("gin: response body already written") + errHijackNotSupported = errors.New("gin: underlying ResponseWriter does not implement http.Hijacker") +) // ResponseWriter ... type ResponseWriter interface { @@ -117,12 +120,21 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if w.size < 0 { w.size = 0 } - return w.ResponseWriter.(http.Hijacker).Hijack() + if hijacker, ok := w.ResponseWriter.(http.Hijacker); ok { + return hijacker.Hijack() + } + return nil, nil, errHijackNotSupported } // CloseNotify implements the http.CloseNotifier interface. +// +// Deprecated: the CloseNotifier interface predates Go's context package. +// New code should use Request.Context instead. func (w *responseWriter) CloseNotify() <-chan bool { - return w.ResponseWriter.(http.CloseNotifier).CloseNotify() + if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok { + return cn.CloseNotify() + } + return nil } // Flush implements the http.Flusher interface. diff --git a/response_writer_test.go b/response_writer_test.go index dfc1d2c60d..02dbe4dd92 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -113,15 +113,12 @@ func TestResponseWriterHijack(t *testing.T) { writer.reset(testWriter) w := ResponseWriter(writer) - assert.Panics(t, func() { - _, _, err := w.Hijack() - require.NoError(t, err) - }) + _, _, err := w.Hijack() + require.ErrorIs(t, err, errHijackNotSupported) assert.True(t, w.Written()) - assert.Panics(t, func() { - w.CloseNotify() - }) + ch := w.CloseNotify() + assert.Nil(t, ch) w.Flush() } @@ -315,3 +312,43 @@ func TestPusherWithoutPusher(t *testing.T) { pusher := w.Pusher() assert.Nil(t, pusher, "Expected pusher to be nil") } + +// mockCloseNotifier is an http.ResponseWriter that implements http.CloseNotifier. +type mockCloseNotifier struct { + *httptest.ResponseRecorder +} + +func (m *mockCloseNotifier) CloseNotify() <-chan bool { + return make(chan bool) +} + +func TestCloseNotifyWithCloseNotifier(t *testing.T) { + rw := &mockCloseNotifier{ResponseRecorder: httptest.NewRecorder()} + w := &responseWriter{} + w.reset(rw) + + ch := w.CloseNotify() + assert.NotNil(t, ch, "Expected CloseNotify channel to be non-nil") +} + +func TestCloseNotifyWithoutCloseNotifier(t *testing.T) { + // httptest.NewRecorder does not implement http.CloseNotifier + rw := httptest.NewRecorder() + w := &responseWriter{} + w.reset(rw) + + ch := w.CloseNotify() + assert.Nil(t, ch, "Expected CloseNotify channel to be nil when underlying writer does not support it") +} + +func TestHijackWithoutHijacker(t *testing.T) { + // httptest.NewRecorder does not implement http.Hijacker + rw := httptest.NewRecorder() + w := &responseWriter{} + w.reset(rw) + + conn, buf, err := w.Hijack() + assert.Nil(t, conn) + assert.Nil(t, buf) + require.ErrorIs(t, err, errHijackNotSupported) +} From fb190d5c5e752c4c5a15f6a8a1ec575a60a726d8 Mon Sep 17 00:00:00 2001 From: Lev Date: Wed, 15 Apr 2026 20:33:43 +0300 Subject: [PATCH 2/2] fix: return non-nil channel when CloseNotifier is not supported --- response_writer.go | 2 +- response_writer_test.go | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/response_writer.go b/response_writer.go index 305322b63e..7a67bfdf6a 100644 --- a/response_writer.go +++ b/response_writer.go @@ -134,7 +134,7 @@ func (w *responseWriter) CloseNotify() <-chan bool { if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok { return cn.CloseNotify() } - return nil + return make(chan bool) } // Flush implements the http.Flusher interface. diff --git a/response_writer_test.go b/response_writer_test.go index 02dbe4dd92..a58068f01e 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -118,7 +118,7 @@ func TestResponseWriterHijack(t *testing.T) { assert.True(t, w.Written()) ch := w.CloseNotify() - assert.Nil(t, ch) + assert.NotNil(t, ch) w.Flush() } @@ -338,7 +338,12 @@ func TestCloseNotifyWithoutCloseNotifier(t *testing.T) { w.reset(rw) ch := w.CloseNotify() - assert.Nil(t, ch, "Expected CloseNotify channel to be nil when underlying writer does not support it") + assert.NotNil(t, ch, "Expected non-nil channel when CloseNotifier is not supported") + select { + case <-ch: + t.Fatal("channel should never fire when CloseNotifier is not supported") + default: + } } func TestHijackWithoutHijacker(t *testing.T) {