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