From f2a1a827901233e1b80860751f113bf994be0bac Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Sun, 23 Oct 2022 11:59:01 -0500 Subject: [PATCH 1/3] Allow for resolution of NPM registry paths that match upstream --- routers/api/packages/api.go | 8 ++++++++ routers/api/packages/npm/npm.go | 12 ++++++++++++ tests/integration/api_packages_npm_test.go | 9 ++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index f6ab961f5ec5d..86911854dba2a 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -215,6 +215,10 @@ func Routes(ctx gocontext.Context) *web.Route { r.Get("", npm.DownloadPackageFile) r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) }) + r.Group("/-/{filename}", func() { + r.Get("", npm.DownloadPackageFile) + r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) + }) r.Group("/-rev/{revision}", func() { r.Delete("", npm.DeletePackage) r.Put("", npm.DeletePreview) @@ -227,6 +231,10 @@ func Routes(ctx gocontext.Context) *web.Route { r.Get("", npm.DownloadPackageFile) r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) }) + r.Group("/-/{filename}", func() { + r.Get("", npm.DownloadPackageFile) + r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) + }) r.Group("/-rev/{revision}", func() { r.Delete("", npm.DeletePackage) r.Put("", npm.DeletePreview) diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index 2989ce6e7f6bd..ba75b3fdb1b72 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "net/http" + "regexp" "strings" "code.gitea.io/gitea/models/db" @@ -28,6 +29,11 @@ import ( // errInvalidTagName indicates an invalid tag name var errInvalidTagName = errors.New("The tag name is invalid") +// npmFileNameRegex is used to fuzzily extract an npm package version from a filename. +// It was expanded from http://json.schemastore.org/package, https://github.com/Masterminds/semver, and https://docs.npmjs.com/cli/v6/using-npm/semver +// To test it see: https://regex101.com/r/OydBJq/5 +var npmFileNameRegex = regexp.MustCompile(`^(?P[\w\-\.]+)-(?Pv?\d+(\.\d+)?(\.\d+)?(-[\w\-]+(\.[\w\-]+)*)?(\+[\w\-]+(\.[\w\-]+)*)?)(?P\.[\w\-]+)$`) + func apiError(ctx *context.Context, status int, obj interface{}) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.JSON(status, map[string]string{ @@ -81,6 +87,12 @@ func DownloadPackageFile(ctx *context.Context) { packageVersion := ctx.Params("version") filename := ctx.Params("filename") + if packageVersion == "" { + matches := npmFileNameRegex.FindStringSubmatch(filename) + idx := npmFileNameRegex.SubexpIndex + packageVersion = matches[idx("version")] + } + s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ diff --git a/tests/integration/api_packages_npm_test.go b/tests/integration/api_packages_npm_test.go index 4fdcf6682e933..b5d3a038706c8 100644 --- a/tests/integration/api_packages_npm_test.go +++ b/tests/integration/api_packages_npm_test.go @@ -127,10 +127,17 @@ func TestPackageNpm(t *testing.T) { b, _ := base64.StdEncoding.DecodeString(data) assert.Equal(t, b, resp.Body.Bytes()) + req = NewRequest(t, "GET", fmt.Sprintf("%s/-/%s", root, filename)) + req = addTokenAuthHeader(req, token) + resp = MakeRequest(t, req, http.StatusOK) + + b, _ = base64.StdEncoding.DecodeString(data) + assert.Equal(t, b, resp.Body.Bytes()) + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) assert.NoError(t, err) assert.Len(t, pvs, 1) - assert.Equal(t, int64(1), pvs[0].DownloadCount) + assert.Equal(t, int64(2), pvs[0].DownloadCount) }) t.Run("PackageMetadata", func(t *testing.T) { From 44ad14395dc98cef0ff796e4e288d43f0c5b81f3 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Sun, 23 Oct 2022 20:25:34 -0500 Subject: [PATCH 2/3] Resolve PR feedback --- routers/api/packages/api.go | 10 ++------ routers/api/packages/npm/npm.go | 28 +++++++++++++++------- tests/integration/api_packages_npm_test.go | 1 - 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 86911854dba2a..321559ca72c4b 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -215,10 +215,7 @@ func Routes(ctx gocontext.Context) *web.Route { r.Get("", npm.DownloadPackageFile) r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) }) - r.Group("/-/{filename}", func() { - r.Get("", npm.DownloadPackageFile) - r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) - }) + r.Get("/-/{filename}", npm.DownloadPackageFile) r.Group("/-rev/{revision}", func() { r.Delete("", npm.DeletePackage) r.Put("", npm.DeletePreview) @@ -231,10 +228,7 @@ func Routes(ctx gocontext.Context) *web.Route { r.Get("", npm.DownloadPackageFile) r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) }) - r.Group("/-/{filename}", func() { - r.Get("", npm.DownloadPackageFile) - r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) - }) + r.Get("/-/{filename}", npm.DownloadPackageFile) r.Group("/-rev/{revision}", func() { r.Delete("", npm.DeletePackage) r.Put("", npm.DeletePreview) diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index ba75b3fdb1b72..8e3d4a4ae6ee5 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -10,7 +10,6 @@ import ( "fmt" "io" "net/http" - "regexp" "strings" "code.gitea.io/gitea/models/db" @@ -29,11 +28,6 @@ import ( // errInvalidTagName indicates an invalid tag name var errInvalidTagName = errors.New("The tag name is invalid") -// npmFileNameRegex is used to fuzzily extract an npm package version from a filename. -// It was expanded from http://json.schemastore.org/package, https://github.com/Masterminds/semver, and https://docs.npmjs.com/cli/v6/using-npm/semver -// To test it see: https://regex101.com/r/OydBJq/5 -var npmFileNameRegex = regexp.MustCompile(`^(?P[\w\-\.]+)-(?Pv?\d+(\.\d+)?(\.\d+)?(-[\w\-]+(\.[\w\-]+)*)?(\+[\w\-]+(\.[\w\-]+)*)?)(?P\.[\w\-]+)$`) - func apiError(ctx *context.Context, status int, obj interface{}) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.JSON(status, map[string]string{ @@ -88,9 +82,25 @@ func DownloadPackageFile(ctx *context.Context) { filename := ctx.Params("filename") if packageVersion == "" { - matches := npmFileNameRegex.FindStringSubmatch(filename) - idx := npmFileNameRegex.SubexpIndex - packageVersion = matches[idx("version")] + pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ + OwnerID: ctx.Package.Owner.ID, + Type: packages_model.TypeNpm, + Name: packages_model.SearchValue{ + ExactMatch: true, + Value: packageNameFromParams(ctx), + }, + HasFileWithName: filename, + IsInternal: util.OptionalBoolFalse, + }) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if len(pvs) != 1 { + apiError(ctx, http.StatusNotFound, nil) + return + } + packageVersion = pvs[0].Version } s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( diff --git a/tests/integration/api_packages_npm_test.go b/tests/integration/api_packages_npm_test.go index b5d3a038706c8..20412fe727cf1 100644 --- a/tests/integration/api_packages_npm_test.go +++ b/tests/integration/api_packages_npm_test.go @@ -131,7 +131,6 @@ func TestPackageNpm(t *testing.T) { req = addTokenAuthHeader(req, token) resp = MakeRequest(t, req, http.StatusOK) - b, _ = base64.StdEncoding.DecodeString(data) assert.Equal(t, b, resp.Body.Bytes()) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) From 7e09ea4afecc928299646afd96554a505d221c64 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Sun, 23 Oct 2022 20:40:27 -0500 Subject: [PATCH 3/3] Change it to use separate function --- routers/api/packages/api.go | 4 +- routers/api/packages/npm/npm.go | 65 ++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 321559ca72c4b..69c8dd6564b78 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -215,7 +215,7 @@ func Routes(ctx gocontext.Context) *web.Route { r.Get("", npm.DownloadPackageFile) r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) }) - r.Get("/-/{filename}", npm.DownloadPackageFile) + r.Get("/-/{filename}", npm.DownloadPackageFileByName) r.Group("/-rev/{revision}", func() { r.Delete("", npm.DeletePackage) r.Put("", npm.DeletePreview) @@ -228,7 +228,7 @@ func Routes(ctx gocontext.Context) *web.Route { r.Get("", npm.DownloadPackageFile) r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) }) - r.Get("/-/{filename}", npm.DownloadPackageFile) + r.Get("/-/{filename}", npm.DownloadPackageFileByName) r.Group("/-rev/{revision}", func() { r.Delete("", npm.DeletePackage) r.Put("", npm.DeletePreview) diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index 8e3d4a4ae6ee5..82dae0cf435d6 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -81,28 +81,6 @@ func DownloadPackageFile(ctx *context.Context) { packageVersion := ctx.Params("version") filename := ctx.Params("filename") - if packageVersion == "" { - pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ - OwnerID: ctx.Package.Owner.ID, - Type: packages_model.TypeNpm, - Name: packages_model.SearchValue{ - ExactMatch: true, - Value: packageNameFromParams(ctx), - }, - HasFileWithName: filename, - IsInternal: util.OptionalBoolFalse, - }) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - if len(pvs) != 1 { - apiError(ctx, http.StatusNotFound, nil) - return - } - packageVersion = pvs[0].Version - } - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ @@ -128,6 +106,49 @@ func DownloadPackageFile(ctx *context.Context) { ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime()) } +// DownloadPackageFileByName finds the version and serves the contents of a package +func DownloadPackageFileByName(ctx *context.Context) { + filename := ctx.Params("filename") + + pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ + OwnerID: ctx.Package.Owner.ID, + Type: packages_model.TypeNpm, + Name: packages_model.SearchValue{ + ExactMatch: true, + Value: packageNameFromParams(ctx), + }, + HasFileWithName: filename, + IsInternal: util.OptionalBoolFalse, + }) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if len(pvs) != 1 { + apiError(ctx, http.StatusNotFound, nil) + return + } + + s, pf, err := packages_service.GetFileStreamByPackageVersion( + ctx, + pvs[0], + &packages_service.PackageFileInfo{ + Filename: filename, + }, + ) + if err != nil { + if err == packages_model.ErrPackageFileNotExist { + apiError(ctx, http.StatusNotFound, err) + return + } + apiError(ctx, http.StatusInternalServerError, err) + return + } + defer s.Close() + + ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime()) +} + // UploadPackage creates a new package func UploadPackage(ctx *context.Context) { npmPackage, err := npm_module.ParsePackage(ctx.Req.Body)