From ebbf2b44c59e2c473ed4ed6b0e044f5daee75e54 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 11 Aug 2022 15:56:34 -0700 Subject: [PATCH] net/http: add a test to verify form tempfiles are deleted The HTTP/1 server deletes multipart form tempfiles after ServeHTTP returns, but the HTTP/2 server does not. Add a test to verify cleanup happens in both cases, temporarily disabled for the HTTP/2 path. For #20253 Updates #25965 Change-Id: Ib753f2761fe73b29321d9d4337dbb5090fd193c2 Reviewed-on: https://go-review.googlesource.com/c/go/+/423194 TryBot-Result: Gopher Robot Run-TryBot: Damien Neil Reviewed-by: Robert Griesemer Reviewed-by: Brad Fitzpatrick --- src/net/http/serve_test.go | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index cb6312d6412..f956e66c44d 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -20,6 +20,7 @@ import ( "io" "log" "math/rand" + "mime/multipart" "net" . "net/http" "net/http/httptest" @@ -6758,3 +6759,68 @@ func TestProcessing(t *testing.T) { t.Errorf("unexpected response; got %q; should start by %q", got, expected) } } + +func TestParseFormCleanup_h1(t *testing.T) { testParseFormCleanup(t, h1Mode) } +func TestParseFormCleanup_h2(t *testing.T) { + t.Skip("https://go.dev/issue/20253") + testParseFormCleanup(t, h2Mode) +} + +func testParseFormCleanup(t *testing.T, h2 bool) { + const maxMemory = 1024 + const key = "file" + + if runtime.GOOS == "windows" { + // Windows sometimes refuses to remove a file that was just closed. + t.Skip("https://go.dev/issue/25965") + } + + setParallel(t) + defer afterTest(t) + cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { + r.ParseMultipartForm(maxMemory) + f, _, err := r.FormFile(key) + if err != nil { + t.Errorf("r.FormFile(%q) = %v", key, err) + return + } + of, ok := f.(*os.File) + if !ok { + t.Errorf("r.FormFile(%q) returned type %T, want *os.File", key, f) + return + } + w.Write([]byte(of.Name())) + })) + defer cst.close() + + fBuf := new(bytes.Buffer) + mw := multipart.NewWriter(fBuf) + mf, err := mw.CreateFormFile(key, "myfile.txt") + if err != nil { + t.Fatal(err) + } + if _, err := mf.Write(bytes.Repeat([]byte("A"), maxMemory*2)); err != nil { + t.Fatal(err) + } + if err := mw.Close(); err != nil { + t.Fatal(err) + } + req, err := NewRequest("POST", cst.ts.URL, fBuf) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", mw.FormDataContentType()) + res, err := cst.c.Do(req) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + fname, err := io.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + cst.close() + if _, err := os.Stat(string(fname)); !errors.Is(err, os.ErrNotExist) { + t.Errorf("file %q exists after HTTP handler returned", string(fname)) + } +}