From 63ebb53fd526021666bd9fab1f9d092380f7a2f4 Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Sat, 22 Oct 2022 20:15:52 +0300 Subject: [PATCH] Add link to user profile in markdown mention only if user exists (#21533) Previously mentioning a user would link to its profile, regardless of whether the user existed. This change tests if the user exists and only if it does - a link to its profile is added. * Fixes #3444 Signed-off-by: Yarden Shoham Co-authored-by: wxiaoguang Co-authored-by: Lunny Xiao --- contrib/pr/checkout.go | 3 ++- modules/markup/html.go | 10 ++++++-- modules/markup/markdown/markdown_test.go | 5 ++++ modules/markup/renderer.go | 12 +++++++++- routers/api/v1/misc/markdown_test.go | 7 ++++++ routers/init.go | 3 ++- services/markup/main_test.go | 19 ++++++++++++++++ services/markup/processorhelper.go | 29 ++++++++++++++++++++++++ services/markup/processorhelper_test.go | 20 ++++++++++++++++ 9 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 services/markup/main_test.go create mode 100644 services/markup/processorhelper.go create mode 100644 services/markup/processorhelper_test.go diff --git a/contrib/pr/checkout.go b/contrib/pr/checkout.go index 09510ac2c575..686a3ddffaac 100644 --- a/contrib/pr/checkout.go +++ b/contrib/pr/checkout.go @@ -33,6 +33,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers" + markup_service "code.gitea.io/gitea/services/markup" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" @@ -112,7 +113,7 @@ func runPR() { log.Printf("[PR] Setting up router\n") // routers.GlobalInit() external.RegisterRenderers() - markup.Init() + markup.Init(markup_service.ProcessorHelper()) c := routers.NormalRoutes(graceful.GetManager().HammerContext()) log.Printf("[PR] Ready for testing !\n") diff --git a/modules/markup/html.go b/modules/markup/html.go index a5606dbb516a..ae00c3905fe8 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -603,8 +603,14 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { start = loc.End continue } - replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mention[1:]), mention, "mention")) - node = node.NextSibling.NextSibling + mentionedUsername := mention[1:] + + if processorHelper.IsUsernameMentionable != nil && processorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) { + replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mentionedUsername), mention, "mention")) + node = node.NextSibling.NextSibling + } else { + node = node.NextSibling + } start = 0 } } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 12c6288c24d1..fbb741d1cd8a 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -38,6 +38,11 @@ func TestMain(m *testing.M) { if err := git.InitSimple(context.Background()); err != nil { log.Fatal("git init failed, err: %v", err) } + markup.Init(&markup.ProcessorHelper{ + IsUsernameMentionable: func(ctx context.Context, username string) bool { + return username == "r-lyeh" + }, + }) os.Exit(m.Run()) } diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 5f69dc72354f..b3289cb3c3b0 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -19,8 +19,18 @@ import ( "code.gitea.io/gitea/modules/setting" ) +type ProcessorHelper struct { + IsUsernameMentionable func(ctx context.Context, username string) bool +} + +var processorHelper ProcessorHelper + // Init initialize regexps for markdown parsing -func Init() { +func Init(ph *ProcessorHelper) { + if ph != nil { + processorHelper = *ph + } + NewSanitizer() if len(setting.Markdown.CustomURLSchemes) > 0 { CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes) diff --git a/routers/api/v1/misc/markdown_test.go b/routers/api/v1/misc/markdown_test.go index 7809fa5cc72a..65ce06027802 100644 --- a/routers/api/v1/misc/markdown_test.go +++ b/routers/api/v1/misc/markdown_test.go @@ -5,6 +5,7 @@ package misc import ( + go_context "context" "io" "net/http" "net/http/httptest" @@ -13,6 +14,7 @@ import ( "testing" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates" @@ -50,6 +52,11 @@ func wrap(ctx *context.Context) *context.APIContext { func TestAPI_RenderGFM(t *testing.T) { setting.AppURL = AppURL + markup.Init(&markup.ProcessorHelper{ + IsUsernameMentionable: func(ctx go_context.Context, username string) bool { + return username == "r-lyeh" + }, + }) options := api.MarkdownOption{ Mode: "gfm", diff --git a/routers/init.go b/routers/init.go index 0f2e993413af..9045437f872b 100644 --- a/routers/init.go +++ b/routers/init.go @@ -41,6 +41,7 @@ import ( "code.gitea.io/gitea/services/automerge" "code.gitea.io/gitea/services/cron" "code.gitea.io/gitea/services/mailer" + markup_service "code.gitea.io/gitea/services/markup" repo_migrations "code.gitea.io/gitea/services/migrations" mirror_service "code.gitea.io/gitea/services/mirror" pull_service "code.gitea.io/gitea/services/pull" @@ -123,7 +124,7 @@ func GlobalInitInstalled(ctx context.Context) { highlight.NewContext() external.RegisterRenderers() - markup.Init() + markup.Init(markup_service.ProcessorHelper()) if setting.EnableSQLite3 { log.Info("SQLite3 support is enabled") diff --git a/services/markup/main_test.go b/services/markup/main_test.go new file mode 100644 index 000000000000..8efd08e69d64 --- /dev/null +++ b/services/markup/main_test.go @@ -0,0 +1,19 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package markup + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models/unittest" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, &unittest.TestOptions{ + GiteaRootPath: filepath.Join("..", ".."), + FixtureFiles: []string{"user.yml"}, + }) +} diff --git a/services/markup/processorhelper.go b/services/markup/processorhelper.go new file mode 100644 index 000000000000..2b1cac2a5b88 --- /dev/null +++ b/services/markup/processorhelper.go @@ -0,0 +1,29 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package markup + +import ( + "context" + + "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" +) + +func ProcessorHelper() *markup.ProcessorHelper { + return &markup.ProcessorHelper{ + IsUsernameMentionable: func(ctx context.Context, username string) bool { + // TODO: cast ctx to modules/context.Context and use IsUserVisibleToViewer + + // Only link if the user actually exists + userExists, err := user.IsUserExist(ctx, 0, username) + if err != nil { + log.Error("Failed to validate user in mention %q exists, assuming it does", username) + userExists = true + } + return userExists + }, + } +} diff --git a/services/markup/processorhelper_test.go b/services/markup/processorhelper_test.go new file mode 100644 index 000000000000..386465bc9189 --- /dev/null +++ b/services/markup/processorhelper_test.go @@ -0,0 +1,20 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package markup + +import ( + "context" + "testing" + + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestProcessorHelper(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + assert.True(t, ProcessorHelper().IsUsernameMentionable(context.Background(), "user10")) + assert.False(t, ProcessorHelper().IsUsernameMentionable(context.Background(), "no-such-user")) +}