Skip to content
8 changes: 8 additions & 0 deletions routers/api/packages/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions routers/api/packages/npm/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"io"
"net/http"
"regexp"
"strings"

"code.gitea.io/gitea/models/db"
Expand All @@ -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<name>[\w\-\.]+)-(?P<version>v?\d+(\.\d+)?(\.\d+)?(-[\w\-]+(\.[\w\-]+)*)?(\+[\w\-]+(\.[\w\-]+)*)?)(?P<ext>\.[\w\-]+)$`)

func apiError(ctx *context.Context, status int, obj interface{}) {
helper.LogAndProcessError(ctx, status, obj, func(message string) {
ctx.JSON(status, map[string]string{
Expand Down Expand Up @@ -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{
Expand Down
9 changes: 8 additions & 1 deletion tests/integration/api_packages_npm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down