fix(release): validate web attachment renames against allowed types (#38314)

This fixes the web release edit flow so renamed release attachments are
validated against `[repository.release] ALLOWED_TYPES`.

Previously, the API attachment edit endpoint already enforced release
attachment type restrictions, but the web release edit form passed
`attachment-edit-*` values into `release_service.UpdateRelease`, which
updated attachment names directly without validating the new filename
against `setting.Repository.Release.AllowedTypes`.

As a result, a user with repository write access could rename an
existing release attachment to a disallowed extension through the web
UI.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Lunny Xiao
2026-07-04 15:02:17 +02:00
committed by GitHub
co-authored by wxiaoguang
parent 7fdfb8d642
commit e4ef995f2a
6 changed files with 93 additions and 78 deletions
+24 -55
View File
@@ -27,7 +27,6 @@ import (
"gitea.dev/modules/util" "gitea.dev/modules/util"
"gitea.dev/modules/web" "gitea.dev/modules/web"
"gitea.dev/routers/web/feed" "gitea.dev/routers/web/feed"
shared_user "gitea.dev/routers/web/shared/user"
"gitea.dev/services/context" "gitea.dev/services/context"
"gitea.dev/services/context/upload" "gitea.dev/services/context/upload"
"gitea.dev/services/forms" "gitea.dev/services/forms"
@@ -338,7 +337,6 @@ func LatestRelease(ctx *context.Context) {
func newReleaseCommon(ctx *context.Context) { func newReleaseCommon(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.release.new_release") ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
ctx.Data["PageIsReleaseList"] = true
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil { if err != nil {
@@ -346,17 +344,8 @@ func newReleaseCommon(ctx *context.Context) {
return return
} }
ctx.Data["Tags"] = tags ctx.Data["Tags"] = tags
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("GetRepoAssignees", err)
return
}
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
upload.AddUploadContext(ctx, "release") upload.AddUploadContext(ctx, "release")
PrepareBranchList(ctx) // for New Release page PrepareBranchList(ctx) // for New Release page
} }
@@ -425,8 +414,8 @@ func GenerateReleaseNotes(ctx *context.Context) {
// NewReleasePost response for creating a release // NewReleasePost response for creating a release
func NewReleasePost(ctx *context.Context) { func NewReleasePost(ctx *context.Context) {
newReleaseCommon(ctx) if ctx.HasError() {
if ctx.Written() { ctx.JSONError(ctx.GetErrMsg())
return return
} }
@@ -450,35 +439,28 @@ func NewReleasePost(ctx *context.Context) {
// Or another choice is "always show the tag-only button" if error occurs. // Or another choice is "always show the tag-only button" if error occurs.
ctx.Data["ShowCreateTagOnlyButton"] = form.TagOnly || rel == nil ctx.Data["ShowCreateTagOnlyButton"] = form.TagOnly || rel == nil
// do some form checks
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplReleaseNew)
return
}
form.Target = util.IfZero(form.Target, ctx.Repo.Repository.DefaultBranch) form.Target = util.IfZero(form.Target, ctx.Repo.Repository.DefaultBranch)
if exist, _ := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, form.Target); !exist { if exist, _ := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, form.Target); !exist {
ctx.RenderWithErrDeprecated(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form) ctx.JSONError(ctx.Tr("form.target_branch_not_exist"))
return return
} }
if !form.TagOnly && form.Title == "" { if !form.TagOnly && form.Title == "" {
// if not "tag only", then the title of the release cannot be empty // if not "tag only", then the title of the release cannot be empty
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form) ctx.JSONError(ctx.Tr("repo.release.title_empty"))
return return
} }
handleTagReleaseError := func(err error) { handleTagReleaseError := func(err error) {
ctx.Data["Err_TagName"] = true
switch { switch {
case release_service.IsErrTagAlreadyExists(err): case release_service.IsErrTagAlreadyExists(err):
ctx.RenderWithErrDeprecated(ctx.Tr("repo.branch.tag_collision", form.TagName), tplReleaseNew, &form) ctx.JSONError(ctx.Tr("repo.branch.tag_collision", form.TagName))
case repo_model.IsErrReleaseAlreadyExist(err): case repo_model.IsErrReleaseAlreadyExist(err):
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form) ctx.JSONError(ctx.Tr("repo.release.tag_name_already_exist"))
case release_service.IsErrInvalidTagName(err): case release_service.IsErrInvalidTagName(err):
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form) ctx.JSONError(ctx.Tr("repo.release.tag_name_invalid"))
case release_service.IsErrProtectedTagName(err): case release_service.IsErrProtectedTagName(err):
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form) ctx.JSONError(ctx.Tr("repo.release.tag_name_protected"))
default: default:
ctx.ServerError("handleTagReleaseError", err) ctx.ServerError("handleTagReleaseError", err)
} }
@@ -497,7 +479,7 @@ func NewReleasePost(ctx *context.Context) {
return return
} }
ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName)) ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName)) ctx.JSONRedirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName))
return return
} }
@@ -522,7 +504,7 @@ func NewReleasePost(ctx *context.Context) {
handleTagReleaseError(err) handleTagReleaseError(err)
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/releases") ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
return return
} }
@@ -530,8 +512,7 @@ func NewReleasePost(ctx *context.Context) {
// old logic: if the release is not a tag (it is a real release), do not update it on the "new release" page // old logic: if the release is not a tag (it is a real release), do not update it on the "new release" page
// add new logic: if tag-only, do not convert the tag to a release // add new logic: if tag-only, do not convert the tag to a release
if form.TagOnly || !rel.IsTag { if form.TagOnly || !rel.IsTag {
ctx.Data["Err_TagName"] = true ctx.JSONError(ctx.Tr("repo.release.tag_name_already_exist"))
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
return return
} }
@@ -547,7 +528,7 @@ func NewReleasePost(ctx *context.Context) {
handleTagReleaseError(err) handleTagReleaseError(err)
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/releases") ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
} }
// EditRelease render release edit page // EditRelease render release edit page
@@ -589,55 +570,39 @@ func EditRelease(ctx *context.Context) {
return return
} }
ctx.Data["attachments"] = rel.Attachments ctx.Data["attachments"] = rel.Attachments
// Get assignees.
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, rel.Repo)
if err != nil {
ctx.ServerError("GetRepoAssignees", err)
return
}
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
ctx.HTML(http.StatusOK, tplReleaseNew) ctx.HTML(http.StatusOK, tplReleaseNew)
} }
// EditReleasePost response for edit release // EditReleasePost response for edit release
func EditReleasePost(ctx *context.Context) { func EditReleasePost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.EditReleaseForm) if ctx.HasError() {
ctx.JSONError(ctx.GetErrMsg())
newReleaseCommon(ctx)
if ctx.Written() {
return return
} }
ctx.Data["Title"] = ctx.Tr("repo.release.edit_release") form := web.GetForm(ctx).(*forms.EditReleaseForm)
ctx.Data["PageIsEditRelease"] = true
tagName := ctx.PathParam("*") tagName := ctx.PathParam("*")
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName) rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
if err != nil { if err != nil {
if repo_model.IsErrReleaseNotExist(err) { if repo_model.IsErrReleaseNotExist(err) {
ctx.NotFound(err) ctx.JSONErrorNotFound(err.Error())
} else { } else {
ctx.ServerError("GetRelease", err) ctx.ServerError("GetRelease", err)
} }
return return
} }
if rel.IsTag { if rel.IsTag {
ctx.NotFound(err) // for a pure tag release, don't allow to edit it as a release ctx.JSONErrorNotFound() // for a pure tag release, don't allow to edit it as a release
return return
} }
ctx.Data["tag_name"] = rel.TagName ctx.Data["tag_name"] = rel.TagName
ctx.Data["tag_target"] = util.IfZero(rel.Target, ctx.Repo.Repository.DefaultBranch) ctx.Data["tag_target"] = util.IfZero(rel.Target, ctx.Repo.Repository.DefaultBranch)
ctx.Data["title"] = rel.Title ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note ctx.Data["content"] = rel.Note
ctx.Data["prerelease"] = rel.IsPrerelease ctx.Data["prerelease"] = rel.IsPrerelease
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplReleaseNew)
return
}
const delPrefix = "attachment-del-" const delPrefix = "attachment-del-"
const editPrefix = "attachment-edit-" const editPrefix = "attachment-edit-"
var addAttachmentUUIDs, delAttachmentUUIDs []string var addAttachmentUUIDs, delAttachmentUUIDs []string
@@ -659,10 +624,14 @@ func EditReleasePost(ctx *context.Context) {
rel.IsPrerelease = form.Prerelease rel.IsPrerelease = form.Prerelease
if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo,
rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments); err != nil { rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments); err != nil {
ctx.ServerError("UpdateRelease", err) if upload.IsErrFileTypeForbidden(err) {
ctx.JSONError(err.Error())
} else {
ctx.ServerError("UpdateRelease", err)
}
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/releases") ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
} }
// DeleteRelease deletes a release // DeleteRelease deletes a release
+13 -11
View File
@@ -4,12 +4,14 @@
package repo package repo
import ( import (
"net/http/httptest"
"testing" "testing"
"gitea.dev/models/db" "gitea.dev/models/db"
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
"gitea.dev/models/unit" "gitea.dev/models/unit"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
"gitea.dev/modules/test"
"gitea.dev/modules/web" "gitea.dev/modules/web"
"gitea.dev/services/context" "gitea.dev/services/context"
"gitea.dev/services/contexttest" "gitea.dev/services/contexttest"
@@ -39,15 +41,15 @@ func TestNewReleasePost(t *testing.T) {
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"]) assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"])
}) })
post := func(t *testing.T, form forms.NewReleaseForm) *context.Context { post := func(t *testing.T, form forms.NewReleaseForm) (*context.Context, *httptest.ResponseRecorder) {
ctx, _ := contexttest.MockContext(t, "user2/repo1/releases/new") ctx, resp := contexttest.MockContext(t, "user2/repo1/releases/new")
contexttest.LoadUser(t, ctx, 2) contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1) contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadGitRepo(t, ctx) contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close() defer ctx.Repo.GitRepo.Close()
web.SetForm(ctx, &form) web.SetForm(ctx, &form)
NewReleasePost(ctx) NewReleasePost(ctx)
return ctx return ctx, resp
} }
loadRelease := func(t *testing.T, tagName string) *repo_model.Release { loadRelease := func(t *testing.T, tagName string) *repo_model.Release {
@@ -70,7 +72,7 @@ func TestNewReleasePost(t *testing.T) {
}) })
t.Run("ReleaseExistsDoUpdate(non-tag)", func(t *testing.T) { t.Run("ReleaseExistsDoUpdate(non-tag)", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{ _, resp := post(t, forms.NewReleaseForm{
TagName: "v1.1", TagName: "v1.1",
Target: "master", Target: "master",
Title: "updated-title", Title: "updated-title",
@@ -80,11 +82,11 @@ func TestNewReleasePost(t *testing.T) {
require.NotNil(t, rel) require.NotNil(t, rel)
assert.False(t, rel.IsTag) assert.False(t, rel.IsTag)
assert.Equal(t, "testing-release", rel.Title) assert.Equal(t, "testing-release", rel.Title)
assert.NotEmpty(t, ctx.Flash.ErrorMsg) assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
}) })
t.Run("ReleaseExistsDoUpdate(tag-only)", func(t *testing.T) { t.Run("ReleaseExistsDoUpdate(tag-only)", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{ ctx, resp := post(t, forms.NewReleaseForm{
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
Target: "master", Target: "master",
Title: "updated-title", Title: "updated-title",
@@ -95,12 +97,12 @@ func TestNewReleasePost(t *testing.T) {
require.NotNil(t, rel) require.NotNil(t, rel)
assert.True(t, rel.IsTag) // the record should not be updated because the request is "tag-only". TODO: need to improve the logic? assert.True(t, rel.IsTag) // the record should not be updated because the request is "tag-only". TODO: need to improve the logic?
assert.Equal(t, "delete-tag", rel.Title) assert.Equal(t, "delete-tag", rel.Title)
assert.NotEmpty(t, ctx.Flash.ErrorMsg) assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"]) // still show the "tag-only" button assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"]) // still show the "tag-only" button
}) })
t.Run("ReleaseExistsDoUpdate(tag-release)", func(t *testing.T) { t.Run("ReleaseExistsDoUpdate(tag-release)", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{ ctx, _ := post(t, forms.NewReleaseForm{
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
Target: "master", Target: "master",
Title: "updated-title", Title: "updated-title",
@@ -114,7 +116,7 @@ func TestNewReleasePost(t *testing.T) {
}) })
t.Run("TagOnly", func(t *testing.T) { t.Run("TagOnly", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{ ctx, _ := post(t, forms.NewReleaseForm{
TagName: "new-tag-only", TagName: "new-tag-only",
Target: "master", Target: "master",
Title: "title", Title: "title",
@@ -128,7 +130,7 @@ func TestNewReleasePost(t *testing.T) {
}) })
t.Run("TagOnlyConflict", func(t *testing.T) { t.Run("TagOnlyConflict", func(t *testing.T) {
ctx := post(t, forms.NewReleaseForm{ _, resp := post(t, forms.NewReleaseForm{
TagName: "v1.1", TagName: "v1.1",
Target: "master", Target: "master",
Title: "title", Title: "title",
@@ -138,7 +140,7 @@ func TestNewReleasePost(t *testing.T) {
rel := loadRelease(t, "v1.1") rel := loadRelease(t, "v1.1")
require.NotNil(t, rel) require.NotNil(t, rel)
assert.False(t, rel.IsTag) assert.False(t, rel.IsTag)
assert.NotEmpty(t, ctx.Flash.ErrorMsg) assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
}) })
} }
+13 -7
View File
@@ -20,9 +20,11 @@ import (
"gitea.dev/modules/graceful" "gitea.dev/modules/graceful"
"gitea.dev/modules/log" "gitea.dev/modules/log"
"gitea.dev/modules/repository" "gitea.dev/modules/repository"
"gitea.dev/modules/setting"
"gitea.dev/modules/storage" "gitea.dev/modules/storage"
"gitea.dev/modules/timeutil" "gitea.dev/modules/timeutil"
"gitea.dev/modules/util" "gitea.dev/modules/util"
"gitea.dev/services/context/upload"
notify_service "gitea.dev/services/notify" notify_service "gitea.dev/services/notify"
) )
@@ -319,13 +321,17 @@ func UpdateRelease(ctx context.Context, doer *user_model.User, gitRepo *git.Repo
} }
for uuid, newName := range editAttachments { for uuid, newName := range editAttachments {
if !deletedUUIDs.Contains(uuid) { if deletedUUIDs.Contains(uuid) {
if err = repo_model.UpdateAttachmentByUUID(ctx, &repo_model.Attachment{ continue
UUID: uuid, }
Name: newName, if err = upload.Verify(nil, newName, setting.Repository.Release.AllowedTypes); err != nil {
}, "name"); err != nil { return err
return err }
} if err = repo_model.UpdateAttachmentByUUID(ctx, &repo_model.Attachment{
UUID: uuid,
Name: newName,
}, "name"); err != nil {
return err
} }
} }
} }
+14
View File
@@ -12,8 +12,11 @@ import (
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
"gitea.dev/modules/gitrepo" "gitea.dev/modules/gitrepo"
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
"gitea.dev/modules/timeutil" "gitea.dev/modules/timeutil"
"gitea.dev/services/attachment" "gitea.dev/services/attachment"
"gitea.dev/services/context/upload"
_ "gitea.dev/models/actions" _ "gitea.dev/models/actions"
@@ -270,6 +273,17 @@ func TestRelease_Update(t *testing.T) {
assert.Equal(t, release.ID, release.Attachments[0].ReleaseID) assert.Equal(t, release.ID, release.Attachments[0].ReleaseID)
assert.Equal(t, "test2.txt", release.Attachments[0].Name) assert.Equal(t, "test2.txt", release.Attachments[0].Name)
defer test.MockVariableValue(&setting.Repository.Release.AllowedTypes, ".zip")()
err = UpdateRelease(t.Context(), user, gitRepo, release, nil, nil, map[string]string{
attach.UUID: "test.exe",
})
assert.Error(t, err)
assert.True(t, upload.IsErrFileTypeForbidden(err))
release.Attachments = nil
assert.NoError(t, repo_model.GetReleaseAttachments(t.Context(), release))
assert.Len(t, release.Attachments, 1)
assert.Equal(t, "test2.txt", release.Attachments[0].Name)
// delete the attachment // delete the attachment
assert.NoError(t, UpdateRelease(t.Context(), user, gitRepo, release, nil, []string{attach.UUID}, nil)) assert.NoError(t, UpdateRelease(t.Context(), user, gitRepo, release, nil, []string{attach.UUID}, nil))
release.Attachments = nil release.Attachments = nil
+1 -1
View File
@@ -13,7 +13,7 @@
</h2> </h2>
{{template "base/alert" .}} {{template "base/alert" .}}
<form class="ui form" action="{{.Link}}" method="post" data-global-init="initReleaseEditForm" <form class="ui form form-fetch-action" action="{{.Link}}" method="post" data-global-init="initReleaseEditForm"
data-existing-tags="{{JsonUtils.EncodeToString .Tags}}" data-existing-tags="{{JsonUtils.EncodeToString .Tags}}"
data-tag-helper="{{ctx.Locale.Tr "repo.release.tag_helper"}}" data-tag-helper="{{ctx.Locale.Tr "repo.release.tag_helper"}}"
data-tag-helper-new="{{ctx.Locale.Tr "repo.release.tag_helper_new"}}" data-tag-helper-new="{{ctx.Locale.Tr "repo.release.tag_helper_new"}}"
+28 -4
View File
@@ -10,6 +10,7 @@ import (
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/setting" "gitea.dev/modules/setting"
"gitea.dev/modules/test" "gitea.dev/modules/test"
"gitea.dev/modules/translation" "gitea.dev/modules/translation"
@@ -39,11 +40,10 @@ func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title st
if draft { if draft {
postData["draft"] = "1" postData["draft"] = "1"
} }
req = NewRequestWithValues(t, "POST", link, postData) req = NewRequestWithValues(t, "POST", link, postData)
resp = session.MakeRequest(t, req, http.StatusOK)
resp = session.MakeRequest(t, req, http.StatusSeeOther) assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()))
test.RedirectURL(resp) // check that redirect URL exists
} }
func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, version, label string, count int) { func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, version, label string, count int) {
@@ -253,3 +253,27 @@ func TestDownloadReleaseAttachment(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
session.MakeRequest(t, req, http.StatusOK) session.MakeRequest(t, req, http.StatusOK)
} }
func TestEditReleaseAttachmentRejectsForbiddenRename(t *testing.T) {
defer tests.PrepareTestEnv(t)()
defer test.MockVariableValue(&setting.Repository.Release.AllowedTypes, ".zip")()
attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 9})
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: attachment.ReleaseID})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
session := loginUser(t, repoOwner.Name)
req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/releases/edit/%s", repo.Link(), release.TagName), map[string]string{
"title": release.Title,
"content": release.Note,
"attachment-edit-" + attachment.UUID: "evil.exe",
})
resp := session.MakeRequest(t, req, http.StatusBadRequest)
errMsg := test.ParseJSONError(resp.Body.Bytes()).ErrorMessage
assert.Equal(t, "This file cannot be uploaded or modified due to a forbidden file extension or type.", errMsg)
attachment = unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: attachment.ID})
assert.NotEqual(t, "evil.exe", attachment.Name)
}