From 69178da59a1b6a881eadf0fc61ac600a4624fcbb Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Tue, 20 Oct 2026 16:41:18 +0200 Subject: [PATCH] An answer from the app without a Content-Type reaches the client without one (closes #127) Go's server adds a Content-Type it guesses from the body when the response's headers have no Content-Type key, which happened to some of the app's answers and not others, as the first part of the body or the headers went out first. When the app sends none, smallwebwaf now sets the key with no value on the client's response, which sends none. Model: opus-5-5 --- internal/proxy/passthrough_test.go | 30 ++++++++++++++ internal/proxy/request.go | 7 ++++ internal/proxy/request_internal_test.go | 55 +++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 internal/proxy/request_internal_test.go diff --git a/internal/proxy/passthrough_test.go b/internal/proxy/passthrough_test.go index 1b34821..abaf334 100644 --- a/internal/proxy/passthrough_test.go +++ b/internal/proxy/passthrough_test.go @@ -192,6 +192,36 @@ func wantRequestFields(t *testing.T, line logLine, host string, sent, received i } } +func TestAddsNoContentTypeTheAppDidNotSend(t *testing.T) { + t.Parallel() + + const typed = "text/plain" + + app := startApp(t, func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/typed" { + w.Header().Set("Content-Type", typed) + } else { + // The key with no value keeps Go's server from adding one. + w.Header()["Content-Type"] = nil + } + + _, _ = io.WriteString(w, "page") + }) + addr, _ := startProxy(t, app.URL, nil) + + got := get(t, addr, "/") + if got.header.Values("Content-Type") != nil { + t.Errorf("client got Content-Type %q, want none", + got.header.Get("Content-Type")) + } + + got = get(t, addr, "/typed") + if got.header.Get("Content-Type") != typed { + t.Errorf("client got Content-Type %q, want %q", + got.header.Get("Content-Type"), typed) + } +} + func TestStreamsTheRequestBody(t *testing.T) { t.Parallel() diff --git a/internal/proxy/request.go b/internal/proxy/request.go index e4e09af..e9620d9 100644 --- a/internal/proxy/request.go +++ b/internal/proxy/request.go @@ -431,6 +431,13 @@ func (rq *request) modifyResponse(res *http.Response) error { return errResponseTooLarge } + // Go's server adds a Content-Type it guesses from the body to a + // response whose headers have no Content-Type key. The key with no + // value sends none, as the app sent none. + if res.Header.Get("Content-Type") == "" { + rq.out.Header()["Content-Type"] = nil + } + res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq} rq.startClientResponseTimeout() diff --git a/internal/proxy/request_internal_test.go b/internal/proxy/request_internal_test.go new file mode 100644 index 0000000..ce26481 --- /dev/null +++ b/internal/proxy/request_internal_test.go @@ -0,0 +1,55 @@ +package proxy + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + + "sneak.berlin/go/smallwebwaf/internal/config" +) + +func TestAnAnswerWithoutAContentTypeGetsNone(t *testing.T) { + t.Parallel() + + srv := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, _ *http.Request) { + rq := &request{ + h: &handler{config: &config.Config{}}, + out: &responseWriter{ResponseWriter: w}, + } + app := &http.Response{ + StatusCode: http.StatusOK, + Header: http.Header{}, + Body: http.NoBody, + } + + err := rq.modifyResponse(app) + if err != nil { + t.Errorf("modifyResponse: %v", err) + } + + // The headers go out with this, the first part of the body, + // which Go's server would guess a Content-Type from. + _, _ = io.WriteString(w, "page") + })) + t.Cleanup(srv.Close) + + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL, + http.NoBody) + if err != nil { + t.Fatalf("new request: %v", err) + } + + res, err := srv.Client().Do(req) + if err != nil { + t.Fatalf("get: %v", err) + } + + _ = res.Body.Close() + + if res.Header.Values("Content-Type") != nil { + t.Errorf("client got Content-Type %q, want none", + res.Header.Get("Content-Type")) + } +}