internal/http3: panic in server handler if given status that is not 3 digits

Our HTTP/1 and HTTP/2 server implementations will panic when WriteHeader
is called from a handler with a status code that is not 3 digits. For
consistency, do this too for HTTP/3.

For golang/go#70914

Change-Id: I65aca23ea186481ae06c7388db487e476a6a6964
Reviewed-on: https://go-review.googlesource.com/c/net/+/759300
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Nicholas S. Husin
2026-03-18 15:34:19 -04:00
committed by Nicholas Husin
parent a4b5ad55af
commit c48da250e2
2 changed files with 61 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ package http3
import (
"context"
"crypto/tls"
"fmt"
"io"
"maps"
"net/http"
@@ -485,6 +486,23 @@ func isInfoStatus(status int) bool {
return status >= 100 && status < 200
}
// checkWriteHeaderCode is a copy of net/http's checkWriteHeaderCode.
func checkWriteHeaderCode(code int) {
// Issue 22880: require valid WriteHeader status codes.
// For now we only enforce that it's three digits.
// In the future we might block things over 599 (600 and above aren't defined
// at http://httpwg.org/specs/rfc7231.html#status.codes).
// But for now any three digits.
//
// We used to send "HTTP/1.1 000 0" on the wire in responses but there's
// no equivalent bogus thing we can realistically send in HTTP/3,
// so we'll consistently panic instead and help people find their bugs
// early. (We can't return an error from WriteHeader even if we wanted to.)
if code < 100 || code > 999 {
panic(fmt.Sprintf("invalid WriteHeader code %v", code))
}
}
func (rw *responseWriter) WriteHeader(statusCode int) {
// TODO: handle sending informational status headers (e.g. 103).
rw.mu.Lock()
@@ -492,6 +510,7 @@ func (rw *responseWriter) WriteHeader(statusCode int) {
if rw.statusCodeSet {
return
}
checkWriteHeaderCode(statusCode)
// Informational headers can be sent multiple times, and should be flushed
// immediately.

View File

@@ -6,6 +6,7 @@ package http3
import (
"errors"
"fmt"
"io"
"maps"
"net/http"
@@ -163,6 +164,47 @@ func TestServerInvalidHeader(t *testing.T) {
})
}
func TestServerInvalidStatus(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
gotpanic := make(chan bool)
ts := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer close(gotpanic)
defer func() {
if e := recover(); e != nil {
got := fmt.Sprintf("%T, %v", e, e)
want := "string, invalid WriteHeader code 0"
if got != want {
t.Errorf("unexpected panic value:\n got: %v\nwant: %v\n", got, want)
}
gotpanic <- true
// Set an explicit 503. This also tests that the
// WriteHeader call panics before it recorded that an
// explicit value was set.
w.WriteHeader(503)
// Verify that writing invalid status will not panic if a
// status is already set anyways.
w.WriteHeader(0)
}
}()
w.WriteHeader(0) // Invalid. Will panic.
}))
tc := ts.connect()
tc.greet()
reqStream := tc.newStream(streamTypeRequest)
reqStream.writeHeaders(requestHeader(nil))
if !<-gotpanic {
t.Error("expected panic in handler")
}
synctest.Wait()
reqStream.wantSomeHeaders(http.Header{
":status": {"503"},
})
reqStream.wantClosed("request is complete")
})
}
func TestServerBody(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ts := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {