From 42096c63e8b4692e76e91700030f1a7892fe7134 Mon Sep 17 00:00:00 2001 From: Alexey Terentyev Date: Sat, 16 Jun 2018 05:57:58 +0300 Subject: [PATCH] Added topics validation, fixed repo topics duplication (#4031) Signed-off-by: Alexey Terentyev --- models/topic.go | 23 +++++++++++++++++++++++ options/locale/locale_en-US.ini | 2 ++ routers/repo/topic.go | 29 ++++++++++++++++++++++++++++- routers/routes/routes.go | 2 +- 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/models/topic.go b/models/topic.go index 3b1737f8a..c1c518de8 100644 --- a/models/topic.go +++ b/models/topic.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/go-xorm/builder" + "regexp" ) func init() { @@ -20,6 +21,8 @@ func init() { ) } +var topicPattern = regexp.MustCompile(`^[a-z0-9+#_.-]+$`) + // Topic represents a topic of repositories type Topic struct { ID int64 @@ -51,6 +54,26 @@ func (err ErrTopicNotExist) Error() string { return fmt.Sprintf("topic is not exist [name: %s]", err.Name) } +func TopicValidator(topic string) bool { + return len(topic) <= 35 && topicPattern.MatchString(topic) +} + +// Remove duplicates from topics slice +func RemoveDuplicateTopics(topics []string) []string { + // Map to record duplicates + saved := make(map[string]struct{}, len(topics)) + i := 0 + for _, v := range topics { + v = strings.TrimSpace(strings.ToLower(v)) + if _, ok := saved[v]; !ok { + saved[v] = struct{}{} + topics[i] = v + i++ + } + } + return topics[:i] +} + // GetTopicByName retrieves topic by name func GetTopicByName(name string) (*Topic, error) { var topic Topic diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8cf6111c6..db6c0b634 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1167,6 +1167,8 @@ branch.protected_deletion_failed = Branch '%s' is protected. It cannot be delete topic.manage_topics = Manage Topics topic.done = Done +topic.count_prompt = You can't select more than 25 topics +topic.format_prompt = Topics must use letter or number and can include hyphen(-), underscore(_), plus(+), hash(#), dot(.) with max length of 35 [org] org_name_holder = Organization Name diff --git a/routers/repo/topic.go b/routers/repo/topic.go index 2a43d53ff..2a5dc8fa5 100644 --- a/routers/repo/topic.go +++ b/routers/repo/topic.go @@ -13,7 +13,7 @@ import ( ) // TopicPost response for creating repository -func TopicPost(ctx *context.Context) { +func TopicsPost(ctx *context.Context) { if ctx.User == nil { ctx.JSON(403, map[string]interface{}{ "message": "Only owners could change the topics.", @@ -27,6 +27,33 @@ func TopicPost(ctx *context.Context) { topics = strings.Split(topicsStr, ",") } + topics = models.RemoveDuplicateTopics(topics) + + if len(topics) > 25 { + log.Error(2, "Incorrect number of topics(max 25): %v", ) + ctx.JSON(422, map[string]interface{}{ + "invalidTopics": topics[:0], + "message": ctx.Tr("repo.topic.count_error"), + }) + return + } + + var invalidTopics = make([]string, 0) + for _, topic := range topics { + if !models.TopicValidator(topic) { + invalidTopics = append(invalidTopics, topic) + } + } + + if len(invalidTopics) > 0 { + log.Error(2, "Invalid topics: %v", invalidTopics) + ctx.JSON(422, map[string]interface{}{ + "invalidTopics": invalidTopics, + "message": ctx.Tr("repo.topic.pattern_error"), + }) + return + } + err := models.SaveTopics(ctx.Repo.Repository.ID, topics...) if err != nil { log.Error(2, "SaveTopics failed: %v", err) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 15b91f159..558564a0d 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -626,7 +626,7 @@ func RegisterRoutes(m *macaron.Macaron) { }, context.RepoAssignment(), context.UnitTypes(), context.LoadRepoUnits(), context.CheckUnit(models.UnitTypeReleases)) m.Group("/:username/:reponame", func() { - m.Post("/topics", repo.TopicPost) + m.Post("/topics", repo.TopicsPost) }, context.RepoAssignment(), reqRepoAdmin) m.Group("/:username/:reponame", func() {