Use relative links for commits, mentions, and issues in markdown (#29427)

Fixes #29404

Use relative links for
- commits
- mentions
- issues

---------

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
KN4CK3R 2024-03-13 11:34:58 +01:00 committed by GitHub
parent 66edc888ee
commit 85c59d6c21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 65 additions and 26 deletions

View File

@ -609,7 +609,7 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
if ok && strings.Contains(mention, "/") { if ok && strings.Contains(mention, "/") {
mentionOrgAndTeam := strings.Split(mention, "/") mentionOrgAndTeam := strings.Split(mention, "/")
if mentionOrgAndTeam[0][1:] == ctx.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") { if mentionOrgAndTeam[0][1:] == ctx.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") {
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, "org", ctx.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "mention")) replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(ctx.Links.Prefix(), "org", ctx.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "mention"))
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling
start = 0 start = 0
continue continue
@ -620,7 +620,7 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
mentionedUsername := mention[1:] mentionedUsername := mention[1:]
if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) { if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mentionedUsername), mention, "mention")) replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(ctx.Links.Prefix(), mentionedUsername), mention, "mention"))
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling
} else { } else {
node = node.NextSibling node = node.NextSibling
@ -898,9 +898,9 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
path = "pulls" path = "pulls"
} }
if ref.Owner == "" { if ref.Owner == "" {
link = createLink(util.URLJoin(setting.AppURL, ctx.Metas["user"], ctx.Metas["repo"], path, ref.Issue), reftext, "ref-issue") link = createLink(util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], path, ref.Issue), reftext, "ref-issue")
} else { } else {
link = createLink(util.URLJoin(setting.AppURL, ref.Owner, ref.Name, path, ref.Issue), reftext, "ref-issue") link = createLink(util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, path, ref.Issue), reftext, "ref-issue")
} }
} }
@ -939,7 +939,7 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
} }
reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
link := createLink(util.URLJoin(setting.AppSubURL, ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit") link := createLink(util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit")
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling
@ -1166,7 +1166,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
continue continue
} }
link := util.URLJoin(setting.AppURL, ctx.Metas["user"], ctx.Metas["repo"], "commit", hash) link := util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], "commit", hash)
replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit")) replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit"))
start = 0 start = 0
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling

View File

@ -287,6 +287,7 @@ func TestRender_IssueIndexPattern_Document(t *testing.T) {
} }
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
ctx.Links.AbsolutePrefix = true
if ctx.Links.Base == "" { if ctx.Links.Base == "" {
ctx.Links.Base = TestRepoURL ctx.Links.Base = TestRepoURL
} }

View File

@ -43,7 +43,8 @@ func TestRender_Commits(t *testing.T) {
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
RelativePath: ".md", RelativePath: ".md",
Links: markup.Links{ Links: markup.Links{
Base: markup.TestRepoURL, AbsolutePrefix: true,
Base: markup.TestRepoURL,
}, },
Metas: localMetas, Metas: localMetas,
}, input) }, input)
@ -96,7 +97,8 @@ func TestRender_CrossReferences(t *testing.T) {
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
RelativePath: "a.md", RelativePath: "a.md",
Links: markup.Links{ Links: markup.Links{
Base: setting.AppSubURL, AbsolutePrefix: true,
Base: setting.AppSubURL,
}, },
Metas: localMetas, Metas: localMetas,
}, input) }, input)
@ -588,7 +590,8 @@ func TestPostProcess_RenderDocument(t *testing.T) {
err := markup.PostProcess(&markup.RenderContext{ err := markup.PostProcess(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
Base: "https://example.com", AbsolutePrefix: true,
Base: "https://example.com",
}, },
Metas: localMetas, Metas: localMetas,
}, strings.NewReader(input), &res) }, strings.NewReader(input), &res)

View File

@ -130,11 +130,11 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
<li><a href="` + baseURLContent + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li> <li><a href="` + baseURLContent + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
<li><a href="` + baseURLContent + `/Tips" rel="nofollow">Tips</a></li> <li><a href="` + baseURLContent + `/Tips" rel="nofollow">Tips</a></li>
</ul> </ul>
<p>See commit <a href="http://localhost:3000/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p> <p>See commit <a href="/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
<p>Ideas and codes</p> <p>Ideas and codes</p>
<ul> <ul>
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" class="ref-issue" rel="nofollow">ocornut/imgui#786</a></li> <li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" class="ref-issue" rel="nofollow">ocornut/imgui#786</a></li>
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/gogits/gogs/issues/786" class="ref-issue" rel="nofollow">#786</a></li> <li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/gogits/gogs/issues/786" class="ref-issue" rel="nofollow">#786</a></li>
<li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li> <li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li>
<li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li> <li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
<li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li> <li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li>

View File

@ -82,9 +82,17 @@ type RenderContext struct {
} }
type Links struct { type Links struct {
Base string AbsolutePrefix bool
BranchPath string Base string
TreePath string BranchPath string
TreePath string
}
func (l *Links) Prefix() string {
if l.AbsolutePrefix {
return setting.AppURL
}
return setting.AppSubURL
} }
func (l *Links) HasBranchInfo() bool { func (l *Links) HasBranchInfo() bool {

View File

@ -117,21 +117,21 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a582
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<span class="emoji" aria-label="thumbs up">👍</span> <span class="emoji" aria-label="thumbs up">👍</span>
<a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a> <a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a>
<a href="http://localhost:3000/mention-user" class="mention">@mention-user</a> test <a href="/mention-user" class="mention">@mention-user</a> test
<a href="http://localhost:3000/user13/repo11/issues/123" class="ref-issue">#123</a> <a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space` space`
assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas)) assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
} }
func TestRenderCommitMessage(t *testing.T) { func TestRenderCommitMessage(t *testing.T) {
expected := `space <a href="http://localhost:3000/mention-user" class="mention">@mention-user</a> ` expected := `space <a href="/mention-user" class="mention">@mention-user</a> `
assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas)) assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas))
} }
func TestRenderCommitMessageLinkSubject(t *testing.T) { func TestRenderCommitMessageLinkSubject(t *testing.T) {
expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="http://localhost:3000/mention-user" class="mention">@mention-user</a>` expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" class="mention">@mention-user</a>`
assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas)) assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas))
} }
@ -155,14 +155,14 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<span class="emoji" aria-label="thumbs up">👍</span> <span class="emoji" aria-label="thumbs up">👍</span>
mail@domain.com mail@domain.com
@mention-user test @mention-user test
<a href="http://localhost:3000/user13/repo11/issues/123" class="ref-issue">#123</a> <a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space space
` `
assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas)) assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas))
} }
func TestRenderMarkdownToHtml(t *testing.T) { func TestRenderMarkdownToHtml(t *testing.T) {
expected := `<p>space <a href="http://localhost:3000/mention-user" rel="nofollow">@mention-user</a><br/> expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/>
/just/a/path.bin /just/a/path.bin
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a> <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
<a href="/file.bin" rel="nofollow">local link</a> <a href="/file.bin" rel="nofollow">local link</a>
@ -179,7 +179,7 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a582
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<span class="emoji" aria-label="thumbs up">👍</span> <span class="emoji" aria-label="thumbs up">👍</span>
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a> <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
<a href="http://localhost:3000/mention-user" rel="nofollow">@mention-user</a> test <a href="/mention-user" rel="nofollow">@mention-user</a> test
#123 #123
space</p> space</p>
` `

View File

@ -34,7 +34,8 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
if err := markdown.RenderRaw(&markup.RenderContext{ if err := markdown.RenderRaw(&markup.RenderContext{
Ctx: ctx, Ctx: ctx,
Links: markup.Links{ Links: markup.Links{
Base: urlPrefix, AbsolutePrefix: true,
Base: urlPrefix,
}, },
}, strings.NewReader(text), ctx.Resp); err != nil { }, strings.NewReader(text), ctx.Resp); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.Error(http.StatusInternalServerError, err.Error())
@ -79,7 +80,8 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
if err := markup.Render(&markup.RenderContext{ if err := markup.Render(&markup.RenderContext{
Ctx: ctx, Ctx: ctx,
Links: markup.Links{ Links: markup.Links{
Base: urlPrefix, AbsolutePrefix: true,
Base: urlPrefix,
}, },
Metas: meta, Metas: meta,
IsWiki: wiki, IsWiki: wiki,

View File

@ -222,7 +222,8 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
body, err := markdown.RenderString(&markup.RenderContext{ body, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx, Ctx: ctx,
Links: markup.Links{ Links: markup.Links{
Base: ctx.Issue.Repo.HTMLURL(), AbsolutePrefix: true,
Base: ctx.Issue.Repo.HTMLURL(),
}, },
Metas: ctx.Issue.Repo.ComposeMetas(ctx), Metas: ctx.Issue.Repo.ComposeMetas(ctx),
}, ctx.Content) }, ctx.Content)

View File

@ -8,6 +8,8 @@ import (
"context" "context"
"fmt" "fmt"
"html/template" "html/template"
"io"
"mime/quotedprintable"
"regexp" "regexp"
"strings" "strings"
"testing" "testing"
@ -19,6 +21,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -67,6 +70,12 @@ func prepareMailerTest(t *testing.T) (doer *user_model.User, repo *repo_model.Re
func TestComposeIssueCommentMessage(t *testing.T) { func TestComposeIssueCommentMessage(t *testing.T) {
doer, _, issue, comment := prepareMailerTest(t) doer, _, issue, comment := prepareMailerTest(t)
markup.Init(&markup.ProcessorHelper{
IsUsernameMentionable: func(ctx context.Context, username string) bool {
return username == doer.Name
},
})
setting.IncomingEmail.Enabled = true setting.IncomingEmail.Enabled = true
defer func() { setting.IncomingEmail.Enabled = false }() defer func() { setting.IncomingEmail.Enabled = false }()
@ -77,7 +86,8 @@ func TestComposeIssueCommentMessage(t *testing.T) {
msgs, err := composeIssueCommentMessages(&mailCommentContext{ msgs, err := composeIssueCommentMessages(&mailCommentContext{
Context: context.TODO(), // TODO: use a correct context Context: context.TODO(), // TODO: use a correct context
Issue: issue, Doer: doer, ActionType: activities_model.ActionCommentIssue, Issue: issue, Doer: doer, ActionType: activities_model.ActionCommentIssue,
Content: "test body", Comment: comment, Content: fmt.Sprintf("test @%s %s#%d body", doer.Name, issue.Repo.FullName(), issue.Index),
Comment: comment,
}, "en-US", recipients, false, "issue comment") }, "en-US", recipients, false, "issue comment")
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, msgs, 2) assert.Len(t, msgs, 2)
@ -96,6 +106,20 @@ func TestComposeIssueCommentMessage(t *testing.T) {
assert.Equal(t, "<user2/repo1/issues/1/comment/2@localhost>", gomailMsg.GetHeader("Message-ID")[0], "Message-ID header doesn't match") assert.Equal(t, "<user2/repo1/issues/1/comment/2@localhost>", gomailMsg.GetHeader("Message-ID")[0], "Message-ID header doesn't match")
assert.Equal(t, "<mailto:"+replyTo+">", gomailMsg.GetHeader("List-Post")[0]) assert.Equal(t, "<mailto:"+replyTo+">", gomailMsg.GetHeader("List-Post")[0])
assert.Len(t, gomailMsg.GetHeader("List-Unsubscribe"), 2) // url + mailto assert.Len(t, gomailMsg.GetHeader("List-Unsubscribe"), 2) // url + mailto
var buf bytes.Buffer
gomailMsg.WriteTo(&buf)
b, err := io.ReadAll(quotedprintable.NewReader(&buf))
assert.NoError(t, err)
// text/plain
assert.Contains(t, string(b), fmt.Sprintf(`( %s )`, doer.HTMLURL()))
assert.Contains(t, string(b), fmt.Sprintf(`( %s )`, issue.HTMLURL()))
// text/html
assert.Contains(t, string(b), fmt.Sprintf(`href="%s"`, doer.HTMLURL()))
assert.Contains(t, string(b), fmt.Sprintf(`href="%s"`, issue.HTMLURL()))
} }
func TestComposeIssueMessage(t *testing.T) { func TestComposeIssueMessage(t *testing.T) {