From 99bb5db1e37a834faecc2ce3bfbae73f759ba3ed Mon Sep 17 00:00:00 2001 From: 7y-9 Date: Mon, 8 Jun 2026 02:50:34 +0800 Subject: [PATCH] fix(needle): use discovered file content type (#9851) Problem: Multipart uploads where the first part was a form field and a later part contained the file used the first part's Content-Type for the file metadata. Root cause: After finding a later part with a filename, parseUpload copied data and MD5 from part2 but read Content-Type from the original part variable. Fix: Read Content-Type from the discovered file part. Reproduction: go test ./weed/storage/needle -run TestParseUploadUsesDiscoveredFilePartContentType -count=1 failed before the fix because the parsed MIME type was text/plain instead of application/x-seaweed-test. Validation: go test ./weed/storage/needle -run TestParseUploadUsesDiscoveredFilePartContentType -count=1; go test ./weed/storage/needle -count=1; git diff --check; git diff --cached --check --- weed/storage/needle/needle_parse_upload.go | 2 +- .../needle/needle_parse_upload_stream_test.go | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/weed/storage/needle/needle_parse_upload.go b/weed/storage/needle/needle_parse_upload.go index 4b80897d7..fc4c5637e 100644 --- a/weed/storage/needle/needle_parse_upload.go +++ b/weed/storage/needle/needle_parse_upload.go @@ -208,7 +208,7 @@ func parseUpload(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error) { // update pu.Data = pu.bytesBuffer.Bytes() pu.FileName = util.CleanWindowsPathBase(fName) - contentType = part.Header.Get("Content-Type") + contentType = part2.Header.Get("Content-Type") pu.ContentMd5 = part2.Header.Get("Content-MD5") part = part2 break diff --git a/weed/storage/needle/needle_parse_upload_stream_test.go b/weed/storage/needle/needle_parse_upload_stream_test.go index 80648ea73..a3cd7ba75 100644 --- a/weed/storage/needle/needle_parse_upload_stream_test.go +++ b/weed/storage/needle/needle_parse_upload_stream_test.go @@ -76,6 +76,50 @@ func TestParseUpload_GzipStreamCount(t *testing.T) { } } +func TestParseUploadUsesDiscoveredFilePartContentType(t *testing.T) { + var body bytes.Buffer + mw := multipart.NewWriter(&body) + + fieldHeader := make(textproto.MIMEHeader) + fieldHeader.Set("Content-Disposition", `form-data; name="description"`) + fieldHeader.Set("Content-Type", "text/plain") + field, err := mw.CreatePart(fieldHeader) + if err != nil { + t.Fatalf("create field part: %v", err) + } + if _, err := field.Write([]byte("metadata")); err != nil { + t.Fatalf("write field part: %v", err) + } + + fileHeader := make(textproto.MIMEHeader) + fileHeader.Set("Content-Disposition", `form-data; name="file"; filename="file.dat"`) + fileHeader.Set("Content-Type", "application/x-seaweed-test") + filePart, err := mw.CreatePart(fileHeader) + if err != nil { + t.Fatalf("create file part: %v", err) + } + if _, err := filePart.Write([]byte("payload")); err != nil { + t.Fatalf("write file part: %v", err) + } + if err := mw.Close(); err != nil { + t.Fatalf("close multipart writer: %v", err) + } + + req := httptest.NewRequest(http.MethodPost, "/upload", bytes.NewReader(body.Bytes())) + req.Header.Set("Content-Type", mw.FormDataContentType()) + + pu, err := ParseUpload(req, 1024, &bytes.Buffer{}) + if err != nil { + t.Fatalf("ParseUpload: %v", err) + } + if pu.FileName != "file.dat" { + t.Fatalf("FileName = %q, want file.dat", pu.FileName) + } + if pu.MimeType != "application/x-seaweed-test" { + t.Fatalf("MimeType = %q, want application/x-seaweed-test", pu.MimeType) + } +} + func gzipBytes(t *testing.T, in []byte) []byte { t.Helper() var buf bytes.Buffer