diff --git a/go.mod b/go.mod index 5ccf962e16..b1e31c3faa 100644 --- a/go.mod +++ b/go.mod @@ -107,7 +107,7 @@ require ( github.com/yuin/goldmark-meta v1.1.0 golang.org/x/crypto v0.11.0 golang.org/x/image v0.9.0 - golang.org/x/net v0.12.0 + golang.org/x/net v0.13.0 golang.org/x/oauth2 v0.10.0 golang.org/x/sys v0.10.0 golang.org/x/text v0.11.0 diff --git a/go.sum b/go.sum index 67c73cb710..5e942457a5 100644 --- a/go.sum +++ b/go.sum @@ -1216,8 +1216,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/models/repo/topic.go b/models/repo/topic.go index ec3de869d5..71302388b9 100644 --- a/models/repo/topic.go +++ b/models/repo/topic.go @@ -22,7 +22,7 @@ func init() { db.RegisterModel(new(RepoTopic)) } -var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`) +var topicPattern = regexp.MustCompile(`^[a-z0-9][-.a-z0-9]*$`) // Topic represents a topic of repositories type Topic struct { diff --git a/models/repo/topic_test.go b/models/repo/topic_test.go index 8a8728168d..aaed91bdd3 100644 --- a/models/repo/topic_test.go +++ b/models/repo/topic_test.go @@ -69,6 +69,7 @@ func TestAddTopic(t *testing.T) { func TestTopicValidator(t *testing.T) { assert.True(t, repo_model.ValidateTopic("12345")) assert.True(t, repo_model.ValidateTopic("2-test")) + assert.True(t, repo_model.ValidateTopic("foo.bar")) assert.True(t, repo_model.ValidateTopic("test-3")) assert.True(t, repo_model.ValidateTopic("first")) assert.True(t, repo_model.ValidateTopic("second-test-topic")) @@ -77,4 +78,5 @@ func TestTopicValidator(t *testing.T) { assert.False(t, repo_model.ValidateTopic("$fourth-test,topic")) assert.False(t, repo_model.ValidateTopic("-fifth-test-topic")) assert.False(t, repo_model.ValidateTopic("sixth-go-project-topic-with-excess-length")) + assert.False(t, repo_model.ValidateTopic(".foo")) } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f39f852f16..91321ee58e 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2507,7 +2507,7 @@ tag.create_success = Tag "%s" has been created. topic.manage_topics = Manage Topics topic.done = Done topic.count_prompt = You cannot select more than 25 topics -topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long. +topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase. find_file.go_to_file = Go to file find_file.no_matching = No matching file found diff --git a/services/user/user_test.go b/services/user/user_test.go index a25804fceb..3f1bf9a0f8 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -4,10 +4,13 @@ package user import ( + "fmt" "path/filepath" + "strings" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -94,6 +97,67 @@ func TestCreateUser(t *testing.T) { assert.NoError(t, DeleteUser(db.DefaultContext, user, false)) } +func TestRenameUser(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 21}) + + t.Run("Non-Local", func(t *testing.T) { + u := &user_model.User{ + Type: user_model.UserTypeIndividual, + LoginType: auth.OAuth2, + } + assert.ErrorIs(t, RenameUser(db.DefaultContext, u, "user_rename"), user_model.ErrUserIsNotLocal{}) + }) + + t.Run("Same username", func(t *testing.T) { + assert.ErrorIs(t, RenameUser(db.DefaultContext, user, user.Name), user_model.ErrUsernameNotChanged{UID: user.ID, Name: user.Name}) + }) + + t.Run("Non usable username", func(t *testing.T) { + usernames := []string{"--diff", "aa.png", ".well-known", "search", "aaa.atom"} + for _, username := range usernames { + t.Run(username, func(t *testing.T) { + assert.Error(t, user_model.IsUsableUsername(username)) + assert.Error(t, RenameUser(db.DefaultContext, user, username)) + }) + } + }) + + t.Run("Only capitalization", func(t *testing.T) { + caps := strings.ToUpper(user.Name) + unittest.AssertNotExistsBean(t, &user_model.User{ID: user.ID, Name: caps}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: user.Name}) + + assert.NoError(t, RenameUser(db.DefaultContext, user, caps)) + + unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID, Name: caps}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: caps}) + }) + + t.Run("Already exists", func(t *testing.T) { + existUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + assert.ErrorIs(t, RenameUser(db.DefaultContext, user, existUser.Name), user_model.ErrUserAlreadyExist{Name: existUser.Name}) + assert.ErrorIs(t, RenameUser(db.DefaultContext, user, existUser.LowerName), user_model.ErrUserAlreadyExist{Name: existUser.LowerName}) + newUsername := fmt.Sprintf("uSEr%d", existUser.ID) + assert.ErrorIs(t, RenameUser(db.DefaultContext, user, newUsername), user_model.ErrUserAlreadyExist{Name: newUsername}) + }) + + t.Run("Normal", func(t *testing.T) { + oldUsername := user.Name + newUsername := "User_Rename" + + assert.NoError(t, RenameUser(db.DefaultContext, user, newUsername)) + unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID, Name: newUsername, LowerName: strings.ToLower(newUsername)}) + + redirectUID, err := user_model.LookupUserRedirect(oldUsername) + assert.NoError(t, err) + assert.EqualValues(t, user.ID, redirectUID) + + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: user.Name}) + }) +} + func TestCreateUser_Issue5882(t *testing.T) { // Init settings _ = setting.Admin diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index ccb230e06f..f126224244 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -307,3 +307,15 @@ func TestPrepareWikiFileName_FirstPage(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, "Home.md", newWikiPath) } + +func TestWebPathConversion(t *testing.T) { + assert.Equal(t, "path/wiki", WebPathToURLPath(WebPath("path/wiki"))) + assert.Equal(t, "wiki", WebPathToURLPath(WebPath("wiki"))) + assert.Equal(t, "", WebPathToURLPath(WebPath(""))) +} + +func TestWebPathFromRequest(t *testing.T) { + assert.Equal(t, WebPath("a%2Fb"), WebPathFromRequest("a/b")) + assert.Equal(t, WebPath("a"), WebPathFromRequest("a")) + assert.Equal(t, WebPath("b"), WebPathFromRequest("a/../b")) +} diff --git a/web_src/js/features/repo-home.js b/web_src/js/features/repo-home.js index 55a2771054..70c225b4ba 100644 --- a/web_src/js/features/repo-home.js +++ b/web_src/js/features/repo-home.js @@ -166,7 +166,7 @@ export function initRepoTopicBar() { rules: [ { type: 'validateTopic', - value: /^[a-z0-9][a-z0-9-]{0,35}$/, + value: /^\s*[a-z0-9][-.a-z0-9]{0,35}\s*$/, prompt: topicPrompts.formatPrompt }, {