From 2158cf6e125540d972bb78b84901f45905fb82c6 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 2 Apr 2026 01:54:14 +0200 Subject: [PATCH] Fix NuGet package upload error handling (#37074) Wrap `zip.NewReader` errors in NuGet `ParsePackageMetaData` and `ExtractPortablePdb` as `ErrInvalidArgument` so invalid packages return HTTP 400 (Bad Request) instead of 500 (Internal Server Error). Add integration test for multipart/form-data NuGet upload path (used by `dotnet nuget push`) which was previously untested. Signed-off-by: wxiaoguang Co-authored-by: Claude (Opus 4.6) Co-authored-by: wxiaoguang --- modules/packages/nuget/metadata.go | 2 +- modules/packages/nuget/symbol_extractor.go | 2 +- tests/integration/api_packages_nuget_test.go | 31 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/modules/packages/nuget/metadata.go b/modules/packages/nuget/metadata.go index 5124627395..ae15e4ec83 100644 --- a/modules/packages/nuget/metadata.go +++ b/modules/packages/nuget/metadata.go @@ -140,7 +140,7 @@ type nuspecPackage struct { func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) { archive, err := zip.NewReader(r, size) if err != nil { - return nil, err + return nil, util.NewInvalidArgumentErrorf("unable to parse package meta: %v", err) } for _, file := range archive.File { diff --git a/modules/packages/nuget/symbol_extractor.go b/modules/packages/nuget/symbol_extractor.go index 2eadee5463..5e398151e8 100644 --- a/modules/packages/nuget/symbol_extractor.go +++ b/modules/packages/nuget/symbol_extractor.go @@ -42,7 +42,7 @@ func (l PortablePdbList) Close() { func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) { archive, err := zip.NewReader(r, size) if err != nil { - return nil, err + return nil, util.NewInvalidArgumentErrorf("unable to extract portable pdb: %v", err) } var pdbs PortablePdbList diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index f29a43f980..cf93c22cd0 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -10,6 +10,7 @@ import ( "encoding/xml" "fmt" "io" + "mime/multipart" "net/http" "net/http/httptest" neturl "net/url" @@ -930,4 +931,34 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNotFound) }) + + t.Run("UploadMultipartForm", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + packageContent := createPackage(packageName, packageVersion).Bytes() + + // Simulate dotnet nuget push which sends multipart/form-data with X-NuGet-ApiKey auth + var body bytes.Buffer + mpw := multipart.NewWriter(&body) + part, err := mpw.CreateFormFile("package", "package.nupkg") + assert.NoError(t, err) + _, err = part.Write(packageContent) + assert.NoError(t, err) + err = mpw.Close() + assert.NoError(t, err) + + req := NewRequestWithBody(t, "PUT", url, &body). + SetHeader("Content-Type", mpw.FormDataContentType()) + addNuGetAPIKeyHeader(req, writeToken) + MakeRequest(t, req, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeNuGet) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + // Clean up + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, packageVersion)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + }) }