From 0245410cdcdefd94dc0030a827f0e5e6a6e3bd16 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Thu, 26 Mar 2026 19:19:09 +0100 Subject: [PATCH] [v15.0/forgejo] fix(api): package name in route not properly unescaped (#11829) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/11822 This pull fixes the issue described in https://codeberg.org/forgejo/forgejo/issues/11427 . The api handler of link/unlink packages use escaped path params to find packages. It causes errors when it comes to npm packages, which contains characters like `@` and `/`. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests for Go changes (can be removed for JavaScript changes) - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I ran... - [x] `make pr-go` before pushing ### Tests for JavaScript changes (can be removed for Go changes) - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. - [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change. *The decision if the pull request will be shown in the release notes is up to the mergers / release team.* The content of the `release-notes/.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead. Co-authored-by: Guangxiong Lin Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11829 Reviewed-by: Mathieu Fenniak Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- routers/api/v1/packages/package.go | 6 +++--- tests/integration/api_packages_npm_test.go | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index 03057c4feb..1f7bf89027 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -249,7 +249,7 @@ func LinkPackage(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParamRaw("type")), ctx.PathParamRaw("name")) + pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.Params("type")), ctx.Params("name")) if err != nil { if errors.Is(err, util.ErrNotExist) { ctx.Error(http.StatusNotFound, "GetPackageByName", err) @@ -259,7 +259,7 @@ func LinkPackage(ctx *context.APIContext) { return } - repo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ctx.PathParamRaw("repo_name")) + repo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ctx.Params("repo_name")) if err != nil { if errors.Is(err, util.ErrNotExist) { ctx.Error(http.StatusNotFound, "GetRepositoryByName", err) @@ -311,7 +311,7 @@ func UnlinkPackage(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParamRaw("type")), ctx.PathParamRaw("name")) + pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.Params("type")), ctx.Params("name")) if err != nil { if errors.Is(err, util.ErrNotExist) { ctx.Error(http.StatusNotFound, "GetPackageByName", err) diff --git a/tests/integration/api_packages_npm_test.go b/tests/integration/api_packages_npm_test.go index 38c7ee54c0..78c683c4e4 100644 --- a/tests/integration/api_packages_npm_test.go +++ b/tests/integration/api_packages_npm_test.go @@ -14,6 +14,7 @@ import ( auth_model "forgejo.org/models/auth" "forgejo.org/models/db" "forgejo.org/models/packages" + unit_model "forgejo.org/models/unit" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" "forgejo.org/modules/packages/npm" @@ -28,6 +29,8 @@ func TestPackageNpm(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + tokenWritePackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWritePackage) token := fmt.Sprintf("Bearer %s", getTokenForLoggedInUser(t, loginUser(t, user.Name), auth_model.AccessTokenScopeWritePackage)) @@ -117,6 +120,22 @@ func TestPackageNpm(t *testing.T) { assert.Equal(t, int64(192), pb.Size) }) + t.Run("RepositoryLink", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // create a repository + repo, _, f := tests.CreateDeclarativeRepo(t, user, "", []unit_model.Type{unit_model.TypeCode}, nil, nil) + defer f() + + // link to public repository + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/npm/%s/-/link/%s", user.Name, url.QueryEscape(packageName), repo.Name)).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusCreated) + + // remove link + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/npm/%s/-/unlink", user.Name, url.QueryEscape(packageName))).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusNoContent) + }) + t.Run("UploadExists", func(t *testing.T) { defer tests.PrintCurrentTest(t)()