1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Clarify path param naming (#32969)

In history (from some legacy frameworks), both `:name` and `name` are
supported as path path name, `:name` is an alias to `name`.

To make code consistent, now we should only use `name` but not `:name`.

Also added panic check in related functions to make sure the name won't
be abused in case some downstreams still use them.
This commit is contained in:
wxiaoguang
2024-12-24 21:47:45 +08:00
committed by GitHub
parent b8b690feb9
commit 2a828e2798
102 changed files with 461 additions and 429 deletions

View File

@@ -9,20 +9,15 @@ import (
"html/template"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/web/middleware"
"github.com/go-chi/chi/v5"
)
type BaseContextKeyType struct{}
@@ -107,93 +102,6 @@ func (b *Base) RemoteAddr() string {
return b.Req.RemoteAddr
}
// PathParam returns the param in request path, eg: "/{var}" => "/a%2fb", then `var == "a/b"`
func (b *Base) PathParam(name string) string {
s, err := url.PathUnescape(b.PathParamRaw(name))
if err != nil && !setting.IsProd {
panic("Failed to unescape path param: " + err.Error() + ", there seems to be a double-unescaping bug")
}
return s
}
// PathParamRaw returns the raw param in request path, eg: "/{var}" => "/a%2fb", then `var == "a%2fb"`
func (b *Base) PathParamRaw(name string) string {
return chi.URLParam(b.Req, strings.TrimPrefix(name, ":"))
}
// PathParamInt64 returns the param in request path as int64
func (b *Base) PathParamInt64(p string) int64 {
v, _ := strconv.ParseInt(b.PathParam(p), 10, 64)
return v
}
// SetPathParam set request path params into routes
func (b *Base) SetPathParam(k, v string) {
chiCtx := chi.RouteContext(b)
chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
}
// FormString returns the first value matching the provided key in the form as a string
func (b *Base) FormString(key string) string {
return b.Req.FormValue(key)
}
// FormStrings returns a string slice for the provided key from the form
func (b *Base) FormStrings(key string) []string {
if b.Req.Form == nil {
if err := b.Req.ParseMultipartForm(32 << 20); err != nil {
return nil
}
}
if v, ok := b.Req.Form[key]; ok {
return v
}
return nil
}
// FormTrim returns the first value for the provided key in the form as a space trimmed string
func (b *Base) FormTrim(key string) string {
return strings.TrimSpace(b.Req.FormValue(key))
}
// FormInt returns the first value for the provided key in the form as an int
func (b *Base) FormInt(key string) int {
v, _ := strconv.Atoi(b.Req.FormValue(key))
return v
}
// FormInt64 returns the first value for the provided key in the form as an int64
func (b *Base) FormInt64(key string) int64 {
v, _ := strconv.ParseInt(b.Req.FormValue(key), 10, 64)
return v
}
// FormBool returns true if the value for the provided key in the form is "1", "true" or "on"
func (b *Base) FormBool(key string) bool {
s := b.Req.FormValue(key)
v, _ := strconv.ParseBool(s)
v = v || strings.EqualFold(s, "on")
return v
}
// FormOptionalBool returns an optional.Some(true) or optional.Some(false) if the value
// for the provided key exists in the form else it returns optional.None[bool]()
func (b *Base) FormOptionalBool(key string) optional.Option[bool] {
value := b.Req.FormValue(key)
if len(value) == 0 {
return optional.None[bool]()
}
s := b.Req.FormValue(key)
v, _ := strconv.ParseBool(s)
v = v || strings.EqualFold(s, "on")
return optional.Some(v)
}
func (b *Base) SetFormString(key, value string) {
_ = b.Req.FormValue(key) // force parse form
b.Req.Form.Set(key, value)
}
// PlainTextBytes renders bytes as plain text
func (b *Base) plainTextInternal(skip, status int, bs []byte) {
statusPrefix := status / 100

View File

@@ -0,0 +1,72 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package context
import (
"strconv"
"strings"
"code.gitea.io/gitea/modules/optional"
)
// FormString returns the first value matching the provided key in the form as a string
func (b *Base) FormString(key string) string {
return b.Req.FormValue(key)
}
// FormStrings returns a string slice for the provided key from the form
func (b *Base) FormStrings(key string) []string {
if b.Req.Form == nil {
if err := b.Req.ParseMultipartForm(32 << 20); err != nil {
return nil
}
}
if v, ok := b.Req.Form[key]; ok {
return v
}
return nil
}
// FormTrim returns the first value for the provided key in the form as a space trimmed string
func (b *Base) FormTrim(key string) string {
return strings.TrimSpace(b.Req.FormValue(key))
}
// FormInt returns the first value for the provided key in the form as an int
func (b *Base) FormInt(key string) int {
v, _ := strconv.Atoi(b.Req.FormValue(key))
return v
}
// FormInt64 returns the first value for the provided key in the form as an int64
func (b *Base) FormInt64(key string) int64 {
v, _ := strconv.ParseInt(b.Req.FormValue(key), 10, 64)
return v
}
// FormBool returns true if the value for the provided key in the form is "1", "true" or "on"
func (b *Base) FormBool(key string) bool {
s := b.Req.FormValue(key)
v, _ := strconv.ParseBool(s)
v = v || strings.EqualFold(s, "on")
return v
}
// FormOptionalBool returns an optional.Some(true) or optional.Some(false) if the value
// for the provided key exists in the form else it returns optional.None[bool]()
func (b *Base) FormOptionalBool(key string) optional.Option[bool] {
value := b.Req.FormValue(key)
if len(value) == 0 {
return optional.None[bool]()
}
s := b.Req.FormValue(key)
v, _ := strconv.ParseBool(s)
v = v || strings.EqualFold(s, "on")
return optional.Some(v)
}
func (b *Base) SetFormString(key, value string) {
_ = b.Req.FormValue(key) // force parse form
b.Req.Form.Set(key, value)
}

View File

@@ -0,0 +1,47 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package context
import (
"net/url"
"strconv"
"strings"
"code.gitea.io/gitea/modules/setting"
"github.com/go-chi/chi/v5"
)
// PathParam returns the param in request path, eg: "/{var}" => "/a%2fb", then `var == "a/b"`
func (b *Base) PathParam(name string) string {
s, err := url.PathUnescape(b.PathParamRaw(name))
if err != nil && !setting.IsProd {
panic("Failed to unescape path param: " + err.Error() + ", there seems to be a double-unescaping bug")
}
return s
}
// PathParamRaw returns the raw param in request path, eg: "/{var}" => "/a%2fb", then `var == "a%2fb"`
func (b *Base) PathParamRaw(name string) string {
if strings.HasPrefix(name, ":") {
setting.PanicInDevOrTesting("path param should not start with ':'")
name = name[1:]
}
return chi.URLParam(b.Req, name)
}
// PathParamInt64 returns the param in request path as int64
func (b *Base) PathParamInt64(p string) int64 {
v, _ := strconv.ParseInt(b.PathParam(p), 10, 64)
return v
}
// SetPathParam set request path params into routes
func (b *Base) SetPathParam(name, value string) {
if strings.HasPrefix(name, ":") {
setting.PanicInDevOrTesting("path param should not start with ':'")
name = name[1:]
}
chi.RouteContext(b).URLParams.Add(name, url.PathEscape(value))
}

View File

@@ -40,7 +40,7 @@ func (org *Organization) CanReadUnit(ctx *Context, unitType unit.Type) bool {
}
func GetOrganizationByParams(ctx *Context) {
orgName := ctx.PathParam(":org")
orgName := ctx.PathParam("org")
var err error
@@ -220,7 +220,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Data["NumTeams"] = len(ctx.Org.Teams)
}
teamName := ctx.PathParam(":team")
teamName := ctx.PathParam("team")
if len(teamName) > 0 {
teamExists := false
for _, team := range ctx.Org.Teams {

View File

@@ -316,8 +316,8 @@ func ComposeGoGetImport(ctx context.Context, owner, repo string) string {
// This is particular a workaround for "go get" command which does not respect
// .netrc file.
func EarlyResponseForGoGetMeta(ctx *Context) {
username := ctx.PathParam(":username")
reponame := strings.TrimSuffix(ctx.PathParam(":reponame"), ".git")
username := ctx.PathParam("username")
reponame := strings.TrimSuffix(ctx.PathParam("reponame"), ".git")
if username == "" || reponame == "" {
ctx.PlainText(http.StatusBadRequest, "invalid repository path")
return
@@ -336,8 +336,8 @@ func EarlyResponseForGoGetMeta(ctx *Context) {
// RedirectToRepo redirect to a differently-named repository
func RedirectToRepo(ctx *Base, redirectRepoID int64) {
ownerName := ctx.PathParam(":username")
previousRepoName := ctx.PathParam(":reponame")
ownerName := ctx.PathParam("username")
previousRepoName := ctx.PathParam("reponame")
repo, err := repo_model.GetRepositoryByID(ctx, redirectRepoID)
if err != nil {
@@ -412,8 +412,8 @@ func RepoAssignment(ctx *Context) {
err error
)
userName := ctx.PathParam(":username")
repoName := ctx.PathParam(":reponame")
userName := ctx.PathParam("username")
repoName := ctx.PathParam("reponame")
repoName = strings.TrimSuffix(repoName, ".git")
if setting.Other.EnableFeed {
repoName = strings.TrimSuffix(repoName, ".rss")
@@ -456,7 +456,7 @@ func RepoAssignment(ctx *Context) {
if strings.HasSuffix(repoName, ".wiki") {
// ctx.Req.URL.Path does not have the preceding appSubURL - any redirect must have this added
// Now we happen to know that all of our paths are: /:username/:reponame/whatever_else
originalRepoName := ctx.PathParam(":reponame")
originalRepoName := ctx.PathParam("reponame")
redirectRepoName := strings.TrimSuffix(repoName, ".wiki")
redirectRepoName += originalRepoName[len(redirectRepoName)+5:]
redirectPath := strings.Replace(

View File

@@ -97,8 +97,8 @@ func AddUploadContext(ctx *context.Context, uploadType string) {
} else if uploadType == "comment" {
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove"
if len(ctx.PathParam(":index")) > 0 {
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + url.PathEscape(ctx.PathParam(":index")) + "/attachments"
if len(ctx.PathParam("index")) > 0 {
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + url.PathEscape(ctx.PathParam("index")) + "/attachments"
} else {
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
}

View File

@@ -33,7 +33,7 @@ func UserAssignmentWeb() func(ctx *Context) {
// UserIDAssignmentAPI returns a middleware to handle context-user assignment for api routes
func UserIDAssignmentAPI() func(ctx *APIContext) {
return func(ctx *APIContext) {
userID := ctx.PathParamInt64(":user-id")
userID := ctx.PathParamInt64("user-id")
if ctx.IsSigned && ctx.Doer.ID == userID {
ctx.ContextUser = ctx.Doer
@@ -59,7 +59,7 @@ func UserAssignmentAPI() func(ctx *APIContext) {
}
func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, string, any)) (contextUser *user_model.User) {
username := ctx.PathParam(":username")
username := ctx.PathParam("username")
if doer != nil && doer.LowerName == strings.ToLower(username) {
contextUser = doer

View File

@@ -53,7 +53,7 @@ func getExpectedReadmeContentsResponse() *api.ContentsResponse {
func TestGetContents(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam(":id", "1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -81,7 +81,7 @@ func TestGetContents(t *testing.T) {
func TestGetContentsOrListForDir(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam(":id", "1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -116,7 +116,7 @@ func TestGetContentsOrListForDir(t *testing.T) {
func TestGetContentsOrListForFile(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam(":id", "1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -144,7 +144,7 @@ func TestGetContentsOrListForFile(t *testing.T) {
func TestGetContentsErrors(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam(":id", "1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -175,7 +175,7 @@ func TestGetContentsErrors(t *testing.T) {
func TestGetContentsOrListErrors(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam(":id", "1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -206,7 +206,7 @@ func TestGetContentsOrListErrors(t *testing.T) {
func TestGetContentsOrListOfEmptyRepos(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user30/empty")
ctx.SetPathParam(":id", "52")
ctx.SetPathParam("id", "52")
contexttest.LoadRepo(t, ctx, 52)
contexttest.LoadUser(t, ctx, 30)
contexttest.LoadGitRepo(t, ctx)
@@ -231,15 +231,15 @@ func TestGetBlobBySHA(t *testing.T) {
defer ctx.Repo.GitRepo.Close()
sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
ctx.SetPathParam(":id", "1")
ctx.SetPathParam(":sha", sha)
ctx.SetPathParam("id", "1")
ctx.SetPathParam("sha", sha)
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
if err != nil {
t.Fail()
}
gbr, err := GetBlobBySHA(ctx, ctx.Repo.Repository, gitRepo, ctx.PathParam(":sha"))
gbr, err := GetBlobBySHA(ctx, ctx.Repo.Repository, gitRepo, ctx.PathParam("sha"))
expectedGBR := &api.GitBlobResponse{
Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK",
Encoding: "base64",

View File

@@ -18,7 +18,7 @@ import (
func TestGetDiffPreview(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam(":id", "1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -140,7 +140,7 @@ func TestGetDiffPreview(t *testing.T) {
func TestGetDiffPreviewErrors(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam(":id", "1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)

View File

@@ -99,7 +99,7 @@ func getExpectedFileResponse() *api.FileResponse {
func TestGetFileResponseFromCommit(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam(":id", "1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)

View File

@@ -25,10 +25,10 @@ func TestGetTreeBySHA(t *testing.T) {
sha := ctx.Repo.Repository.DefaultBranch
page := 1
perPage := 10
ctx.SetPathParam(":id", "1")
ctx.SetPathParam(":sha", sha)
ctx.SetPathParam("id", "1")
ctx.SetPathParam("sha", sha)
tree, err := GetTreeBySHA(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam(":sha"), page, perPage, true)
tree, err := GetTreeBySHA(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("sha"), page, perPage, true)
assert.NoError(t, err)
expectedTree := &api.GitTreeResponse{
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",