diff --git a/models/activities/action.go b/models/activities/action.go index 43da557fff398..e74deef1df4cf 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -200,7 +200,7 @@ func (a *Action) LoadActUser(ctx context.Context) { } } -func (a *Action) loadRepo(ctx context.Context) { +func (a *Action) LoadRepo(ctx context.Context) { if a.Repo != nil { return } @@ -250,7 +250,7 @@ func (a *Action) GetActDisplayNameTitle(ctx context.Context) string { // GetRepoUserName returns the name of the action repository owner. func (a *Action) GetRepoUserName(ctx context.Context) string { - a.loadRepo(ctx) + a.LoadRepo(ctx) if a.Repo == nil { return "(non-existing-repo)" } @@ -265,7 +265,7 @@ func (a *Action) ShortRepoUserName(ctx context.Context) string { // GetRepoName returns the name of the action repository. func (a *Action) GetRepoName(ctx context.Context) string { - a.loadRepo(ctx) + a.LoadRepo(ctx) if a.Repo == nil { return "(non-existing-repo)" } @@ -644,7 +644,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error { } if repoChanged { - act.loadRepo(ctx) + act.LoadRepo(ctx) repo = act.Repo // check repo owner exist. diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index 751550f37a6b6..67a77ceb13f36 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -7,8 +7,8 @@ import ( "context" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/renderhelper" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "xorm.io/builder" @@ -112,12 +112,8 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu } var err error - rctx := markup.NewRenderContext(ctx). - WithRepoFacade(issue.Repo). - WithLinks(markup.Links{Base: issue.Repo.Link()}). - WithMetas(issue.Repo.ComposeMetas(ctx)) - if comment.RenderedContent, err = markdown.RenderString(rctx, - comment.Content); err != nil { + rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo) + if comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content); err != nil { return nil, err } } diff --git a/models/renderhelper/commit_checker.go b/models/renderhelper/commit_checker.go new file mode 100644 index 0000000000000..4815643e67348 --- /dev/null +++ b/models/renderhelper/commit_checker.go @@ -0,0 +1,53 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "context" + "io" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/log" +) + +type commitChecker struct { + ctx context.Context + commitCache map[string]bool + gitRepoFacade gitrepo.Repository + + gitRepo *git.Repository + gitRepoCloser io.Closer +} + +func newCommitChecker(ctx context.Context, gitRepo gitrepo.Repository) *commitChecker { + return &commitChecker{ctx: ctx, commitCache: make(map[string]bool), gitRepoFacade: gitRepo} +} + +func (c *commitChecker) Close() error { + if c != nil && c.gitRepoCloser != nil { + return c.gitRepoCloser.Close() + } + return nil +} + +func (c *commitChecker) IsCommitIDExisting(commitID string) bool { + exist, inCache := c.commitCache[commitID] + if inCache { + return exist + } + + if c.gitRepo == nil { + r, closer, err := gitrepo.RepositoryFromContextOrOpen(c.ctx, c.gitRepoFacade) + if err != nil { + log.Error("unable to open repository: %s Error: %v", gitrepo.RepoGitURL(c.gitRepoFacade), err) + return false + } + c.gitRepo, c.gitRepoCloser = r, closer + } + + exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashs with gogit edition. + c.commitCache[commitID] = exist + return exist +} diff --git a/models/renderhelper/repo_comment.go b/models/renderhelper/repo_comment.go new file mode 100644 index 0000000000000..6bd5e91ad1768 --- /dev/null +++ b/models/renderhelper/repo_comment.go @@ -0,0 +1,73 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "context" + "fmt" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/util" +) + +type RepoComment struct { + ctx *markup.RenderContext + opts RepoCommentOptions + + commitChecker *commitChecker + repoLink string +} + +func (r *RepoComment) CleanUp() { + _ = r.commitChecker.Close() +} + +func (r *RepoComment) IsCommitIDExisting(commitID string) bool { + return r.commitChecker.IsCommitIDExisting(commitID) +} + +func (r *RepoComment) ResolveLink(link string, likeType markup.LinkType) (finalLink string) { + switch likeType { + case markup.LinkTypeApp: + finalLink = r.ctx.ResolveLinkApp(link) + default: + finalLink = r.ctx.ResolveLinkRelative(r.repoLink, r.opts.CurrentRefPath, link) + } + return finalLink +} + +var _ markup.RenderHelper = (*RepoComment)(nil) + +type RepoCommentOptions struct { + DeprecatedRepoName string // it is only a patch for the non-standard "markup" api + DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api + CurrentRefPath string // eg: "branch/main" or "commit/11223344" +} + +func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repository, opts ...RepoCommentOptions) *markup.RenderContext { + helper := &RepoComment{ + repoLink: repo.Link(), + opts: util.OptionalArg(opts), + } + rctx := markup.NewRenderContext(ctx) + helper.ctx = rctx + if repo != nil { + helper.repoLink = repo.Link() + helper.commitChecker = newCommitChecker(ctx, repo) + rctx = rctx.WithMetas(repo.ComposeMetas(ctx)) + } else { + // this is almost dead code, only to pass the incorrect tests + helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName) + rctx = rctx.WithMetas(map[string]string{ + "user": helper.opts.DeprecatedOwnerName, + "repo": helper.opts.DeprecatedRepoName, + + "markdownLineBreakStyle": "comment", + "markupAllowShortIssuePattern": "true", + }) + } + rctx = rctx.WithHelper(helper) + return rctx +} diff --git a/models/renderhelper/repo_comment_test.go b/models/renderhelper/repo_comment_test.go new file mode 100644 index 0000000000000..d2f39d65a6df3 --- /dev/null +++ b/models/renderhelper/repo_comment_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package renderhelper + +import ( + "context" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" + + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, &unittest.TestOptions{ + FixtureFiles: []string{"repository.yml", "user.yml"}, + }) +} + +func TestRepoComment(t *testing.T) { + unittest.PrepareTestEnv(t) + markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true + markup.Init(&markup.RenderHelperFuncs{ + IsUsernameMentionable: func(ctx context.Context, username string) bool { + return username == "user2" + }, + }) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + rctx := NewRenderContextRepoComment(context.Background(), repo1).WithMarkupType(markdown.MarkupName) + rendered, err := markup.RenderString(rctx, ` +65f1bf27bc3bf70f64657658635e66094edbcb4d +#1 +@user2 +`) + assert.NoError(t, err) + assert.Equal(t, + `
+`, rendered) + + rctx = NewRenderContextRepoComment(context.Background(), repo1).WithMarkupType(markdown.MarkupName) + rendered, err = markup.RenderString(rctx, ` +[/test](/test) +[./test](./test) +![/image](/image) +![./image](./image) +`) + assert.NoError(t, err) + assert.Equal(t, + ` +`, rendered) + + rctx = NewRenderContextRepoComment(context.Background(), repo1, RepoCommentOptions{CurrentRefPath: "/commit/1234"}). + WithMarkupType(markdown.MarkupName) + rendered, err = markup.RenderString(rctx, ` +[/test](/test) +![/image](/image) +`) + assert.NoError(t, err) + assert.Equal(t, `/test
+
`
- rawLink := ` `
+ rawLink := ` `
// Try to get the user translation
if locale, ok := ctx.Value(translation.ContextKey).(translation.Locale); ok {
diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go
index 98708e99b8622..03242e569ede5 100644
--- a/modules/markup/external/external.go
+++ b/modules/markup/external/external.go
@@ -79,8 +79,8 @@ func envMark(envName string) string {
func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
var (
command = strings.NewReplacer(
- envMark("GITEA_PREFIX_SRC"), ctx.RenderOptions.Links.SrcLink(),
- envMark("GITEA_PREFIX_RAW"), ctx.RenderOptions.Links.RawLink(),
+ envMark("GITEA_PREFIX_SRC"), ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault),
+ envMark("GITEA_PREFIX_RAW"), ctx.RenderHelper.ResolveLink("", markup.LinkTypeRaw),
).Replace(p.Command)
commands = strings.Fields(command)
args = commands[1:]
@@ -112,14 +112,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
args = append(args, f.Name())
}
- processCtx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.RenderOptions.Links.SrcLink()))
+ processCtx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault)))
defer finished()
cmd := exec.CommandContext(processCtx, commands[0], args...)
cmd.Env = append(
os.Environ(),
- "GITEA_PREFIX_SRC="+ctx.RenderOptions.Links.SrcLink(),
- "GITEA_PREFIX_RAW="+ctx.RenderOptions.Links.RawLink(),
+ "GITEA_PREFIX_SRC="+ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault),
+ "GITEA_PREFIX_RAW="+ctx.RenderHelper.ResolveLink("", markup.LinkTypeRaw),
)
if !p.IsInputFile {
cmd.Stdin = input
diff --git a/modules/markup/html.go b/modules/markup/html.go
index e8799c401c537..0b1e9b32242c2 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -260,7 +260,6 @@ func RenderEmoji(ctx *RenderContext, content string) (string, error) {
}
func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output io.Writer) error {
- defer ctx.Cancel()
// FIXME: don't read all content to memory
rawHTML, err := io.ReadAll(input)
if err != nil {
@@ -396,7 +395,7 @@ func createLink(ctx *RenderContext, href, content, class string) *html.Node {
Data: atom.A.String(),
Attr: []html.Attribute{{Key: "href", Val: href}},
}
- if !RenderBehaviorForTesting.DisableInternalAttributes {
+ if !RenderBehaviorForTesting.DisableAdditionalAttributes {
a.Attr = append(a.Attr, html.Attribute{Key: "data-markdown-generated-content"})
}
if class != "" {
diff --git a/modules/markup/html_codepreview.go b/modules/markup/html_codepreview.go
index 68886a3434d0a..1d6d9a61091aa 100644
--- a/modules/markup/html_codepreview.go
+++ b/modules/markup/html_codepreview.go
@@ -51,7 +51,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
opts.LineStart, opts.LineStop = lineStart, lineStop
- h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx, opts)
+ h, err := DefaultRenderHelperFuncs.RenderRepoFileCodePreview(ctx, opts)
return m[0], m[1], h, err
}
diff --git a/modules/markup/html_codepreview_test.go b/modules/markup/html_codepreview_test.go
index 7c0db59d06acc..3d99348ef18d5 100644
--- a/modules/markup/html_codepreview_test.go
+++ b/modules/markup/html_codepreview_test.go
@@ -16,16 +16,16 @@ import (
)
func TestRenderCodePreview(t *testing.T) {
- markup.Init(&markup.ProcessorHelper{
- RenderRepoFileCodePreview: func(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) {
+ markup.Init(&markup.RenderHelperFuncs{
+ RenderRepoFileCodePreview: func(ctx context.Context, options markup.RenderCodePreviewOptions) (template.HTML, error) {
return " code preview ", nil
},
})
test := func(input, expected string) {
- buffer, err := markup.RenderString(markup.NewRenderContext(context.Background()).WithMarkupType(markdown.MarkupName), input)
+ buffer, err := markup.RenderString(markup.NewTestRenderContext().WithMarkupType(markdown.MarkupName), input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
}
test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "code preview ")
- test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20 `) + test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20 `) } diff --git a/modules/markup/html_commit.go b/modules/markup/html_commit.go index 0649f84664073..358e7b06ba538 100644 --- a/modules/markup/html_commit.go +++ b/modules/markup/html_commit.go @@ -4,13 +4,10 @@ package markup import ( - "io" "slices" "strings" "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/gitrepo" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" "golang.org/x/net/html" @@ -163,15 +160,12 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) { // hashCurrentPatternProcessor renders SHA1 strings to corresponding links that // are assumed to be in the same repository. func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) { - if ctx.RenderOptions.Metas == nil || ctx.RenderOptions.Metas["user"] == "" || ctx.RenderOptions.Metas["repo"] == "" || (ctx.RenderHelper.repoFacade == nil && ctx.RenderHelper.gitRepo == nil) { + if ctx.RenderOptions.Metas == nil || ctx.RenderOptions.Metas["user"] == "" || ctx.RenderOptions.Metas["repo"] == "" || ctx.RenderHelper == nil { return } start := 0 next := node.NextSibling - if ctx.RenderHelper.shaExistCache == nil { - ctx.RenderHelper.shaExistCache = make(map[string]bool) - } for node != nil && node != next && start < len(node.Data) { m := globalVars().hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:]) if m == nil { @@ -189,35 +183,12 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) { // as used by git and github for linking and thus we have to do similar. // Because of this, we check to make sure that a matched hash is actually // a commit in the repository before making it a link. - - // check cache first - exist, inCache := ctx.RenderHelper.shaExistCache[hash] - if !inCache { - if ctx.RenderHelper.gitRepo == nil { - var err error - var closer io.Closer - ctx.RenderHelper.gitRepo, closer, err = gitrepo.RepositoryFromContextOrOpen(ctx, ctx.RenderHelper.repoFacade) - if err != nil { - log.Error("unable to open repository: %s Error: %v", gitrepo.RepoGitURL(ctx.RenderHelper.repoFacade), err) - return - } - ctx.AddCancel(func() { - _ = closer.Close() - ctx.RenderHelper.gitRepo = nil - }) - } - - // Don't use IsObjectExist since it doesn't support short hashs with gogit edition. - exist = ctx.RenderHelper.gitRepo.IsReferenceExist(hash) - ctx.RenderHelper.shaExistCache[hash] = exist - } - - if !exist { + if !ctx.RenderHelper.IsCommitIDExisting(hash) { start = m[3] continue } - link := util.URLJoin(ctx.RenderOptions.Links.Prefix(), ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], "commit", hash) + link := ctx.RenderHelper.ResolveLink(util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], "commit", hash), LinkTypeApp) replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit")) start = 0 node = node.NextSibling.NextSibling diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 7b143664fe81d..7f2057a3438eb 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -4,7 +4,6 @@ package markup import ( - "context" "fmt" "strconv" "strings" @@ -34,8 +33,7 @@ func numericIssueLink(baseURL, class string, index int, marker string) string { // link an HTML link func link(href, class, contents string) string { - extra := ` data-markdown-generated-content=""` - extra += util.Iif(class != "", ` class="`+class+`"`, "") + extra := util.Iif(class != "", ` class="`+class+`"`, "") return fmt.Sprintf(`%s`, href, extra, contents) } @@ -69,22 +67,11 @@ var localMetas = map[string]string{ "markupAllowShortIssuePattern": "true", } -var localWikiMetas = map[string]string{ - "user": "test-owner", - "repo": "test-repo", - "markupContentMode": "wiki", -} - func TestRender_IssueIndexPattern(t *testing.T) { // numeric: render inputs without valid mentions test := func(s string) { - testRenderIssueIndexPattern(t, s, s, &RenderContext{ - ctx: context.Background(), - }) - testRenderIssueIndexPattern(t, s, s, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: numericMetas}, - }) + testRenderIssueIndexPattern(t, s, s, NewTestRenderContext()) + testRenderIssueIndexPattern(t, s, s, NewTestRenderContext(numericMetas)) } // should not render anything when there are no mentions @@ -132,10 +119,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) { links[i] = numericIssueLink(util.URLJoin(TestRepoURL, path), "ref-issue", index, marker) } expectedNil := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: localMetas}, - }) + testRenderIssueIndexPattern(t, s, expectedNil, NewTestRenderContext(TestAppURL, localMetas)) class := "ref-issue" if isExternal { @@ -146,10 +130,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) { links[i] = numericIssueLink(prefix, class, index, marker) } expectedNum := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: numericMetas}, - }) + testRenderIssueIndexPattern(t, s, expectedNum, NewTestRenderContext(TestAppURL, numericMetas)) } // should render freestanding mentions @@ -183,10 +164,7 @@ func TestRender_IssueIndexPattern3(t *testing.T) { // alphanumeric: render inputs without valid mentions test := func(s string) { - testRenderIssueIndexPattern(t, s, s, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: alphanumericMetas}, - }) + testRenderIssueIndexPattern(t, s, s, NewTestRenderContext(alphanumericMetas)) } test("") test("this is a test") @@ -216,10 +194,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) { links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name) } expected := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expected, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: alphanumericMetas}, - }) + testRenderIssueIndexPattern(t, s, expected, NewTestRenderContext(alphanumericMetas)) } test("OTT-1234 test", "%s test", "OTT-1234") test("test T-12 issue", "test %s issue", "T-12") @@ -239,10 +214,7 @@ func TestRender_IssueIndexPattern5(t *testing.T) { } expected := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expected, &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }) + testRenderIssueIndexPattern(t, s, expected, NewTestRenderContext(metas)) } test("abc ISSUE-123 def", "abc %s def", @@ -263,10 +235,7 @@ func TestRender_IssueIndexPattern5(t *testing.T) { []string{"ISSUE-123"}, ) - testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: regexpMetas}, - }) + testRenderIssueIndexPattern(t, "will not match", "will not match", NewTestRenderContext(regexpMetas)) } func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) { @@ -278,18 +247,9 @@ func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) { "style": IssueNameStyleNumeric, } - testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }) - testRenderIssueIndexPattern(t, "#1312", "#1312", &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }) - testRenderIssueIndexPattern(t, "!1", "!1", &RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }) + testRenderIssueIndexPattern(t, "#1", "#1", NewTestRenderContext(metas)) + testRenderIssueIndexPattern(t, "#1312", "#1312", NewTestRenderContext(metas)) + testRenderIssueIndexPattern(t, "!1", "!1", NewTestRenderContext(metas)) } func TestRender_RenderIssueTitle(t *testing.T) { @@ -300,20 +260,12 @@ func TestRender_RenderIssueTitle(t *testing.T) { "repo": "someRepo", "style": IssueNameStyleNumeric, } - actual, err := RenderIssueTitle(&RenderContext{ - ctx: context.Background(), - RenderOptions: RenderOptions{Metas: metas}, - }, "#1") + actual, err := RenderIssueTitle(NewTestRenderContext(metas), "#1") assert.NoError(t, err) assert.Equal(t, "#1", actual) } func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { - ctx.RenderOptions.Links.AbsolutePrefix = true - if ctx.RenderOptions.Links.Base == "" { - ctx.RenderOptions.Links.Base = TestRepoURL - } - var buf strings.Builder err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf) assert.NoError(t, err) @@ -325,20 +277,12 @@ func TestRender_AutoLink(t *testing.T) { test := func(input, expected string) { var buffer strings.Builder - err := PostProcess(&RenderContext{ - ctx: context.Background(), - - RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}}, - }, strings.NewReader(input), &buffer) + err := PostProcess(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) assert.Equal(t, err, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) buffer.Reset() - err = PostProcess(&RenderContext{ - ctx: context.Background(), - - RenderOptions: RenderOptions{Metas: localWikiMetas, Links: Links{Base: TestRepoURL}}, - }, strings.NewReader(input), &buffer) + err = PostProcess(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) assert.Equal(t, err, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) } @@ -360,14 +304,10 @@ func TestRender_AutoLink(t *testing.T) { func TestRender_FullIssueURLs(t *testing.T) { setting.AppURL = TestAppURL - defer testModule.MockVariableValue(&RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer testModule.MockVariableValue(&RenderBehaviorForTesting.DisableAdditionalAttributes, true)() test := func(input, expected string) { var result strings.Builder - err := postProcess(&RenderContext{ - ctx: context.Background(), - - RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}}, - }, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) + err := postProcess(NewTestRenderContext(localMetas), []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) assert.NoError(t, err) assert.Equal(t, expected, result.String()) } diff --git a/modules/markup/html_issue.go b/modules/markup/html_issue.go index a75a8b3290066..e64ec76c3d2fc 100644 --- a/modules/markup/html_issue.go +++ b/modules/markup/html_issue.go @@ -136,9 +136,11 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { // Gitea will redirect on click as appropriate. issuePath := util.Iif(ref.IsPull, "pulls", "issues") if ref.Owner == "" { - link = createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], issuePath, ref.Issue), reftext, "ref-issue") + linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], issuePath, ref.Issue), LinkTypeApp) + link = createLink(ctx, linkHref, reftext, "ref-issue") } else { - link = createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ref.Owner, ref.Name, issuePath, ref.Issue), reftext, "ref-issue") + linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, issuePath, ref.Issue), LinkTypeApp) + link = createLink(ctx, linkHref, reftext, "ref-issue") } } @@ -177,7 +179,8 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) { } reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) - link := createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit") + linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha), LinkTypeApp) + link := createLink(ctx, linkHref, reftext, "commit") replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) node = node.NextSibling.NextSibling diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go index f6700161b53f8..5fd38b63cd370 100644 --- a/modules/markup/html_link.go +++ b/modules/markup/html_link.go @@ -6,37 +6,14 @@ package markup import ( "net/url" "path" - "path/filepath" "strings" "code.gitea.io/gitea/modules/markup/common" - "code.gitea.io/gitea/modules/util" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) -func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) { - isAnchorFragment := link != "" && link[0] == '#' - if !isAnchorFragment && !IsFullURLString(link) { - linkBase := ctx.RenderOptions.Links.Base - if ctx.IsMarkupContentWiki() { - // no need to check if the link should be resolved as a wiki link or a wiki raw link - // just use wiki link here, and it will be redirected to a wiki raw link if necessary - linkBase = ctx.RenderOptions.Links.WikiLink() - } else if ctx.RenderOptions.Links.BranchPath != "" || ctx.RenderOptions.Links.TreePath != "" { - // if there is no BranchPath, then the link will be something like "/owner/repo/src/{the-file-path}" - // and then this link will be handled by the "legacy-ref" code and be redirected to the default branch like "/owner/repo/src/branch/main/{the-file-path}" - linkBase = ctx.RenderOptions.Links.SrcLink() - } - link, resolved = util.URLJoin(linkBase, link), true - } - if isAnchorFragment && userContentAnchorPrefix != "" { - link, resolved = userContentAnchorPrefix+link[1:], true - } - return link, resolved -} - func shortLinkProcessor(ctx *RenderContext, node *html.Node) { next := node.NextSibling for node != nil && node != next { @@ -116,7 +93,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { name += tail image := false - ext := filepath.Ext(link) + ext := path.Ext(link) switch ext { // fast path: empty string, ignore case "": @@ -139,6 +116,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { if image { link = strings.ReplaceAll(link, " ", "+") } else { + // the hacky wiki name encoding: space to "-" link = strings.ReplaceAll(link, " ", "-") // FIXME: it should support dashes in the link, eg: "the-dash-support.-" } if !strings.Contains(link, "/") { @@ -146,9 +124,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { } } if image { - if !absoluteLink { - link = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), link) - } + link = ctx.RenderHelper.ResolveLink(link, LinkTypeMedia) title := props["title"] if title == "" { title = props["alt"] @@ -174,7 +150,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { childNode.Attr = childNode.Attr[:2] } } else { - link, _ = ResolveLink(ctx, link, "") + link = ctx.RenderHelper.ResolveLink(link, LinkTypeDefault) childNode.Type = html.TextNode childNode.Data = name } diff --git a/modules/markup/html_mention.go b/modules/markup/html_mention.go index 4243eeb20f93b..fffa12e7b740e 100644 --- a/modules/markup/html_mention.go +++ b/modules/markup/html_mention.go @@ -33,7 +33,8 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { if ok && strings.Contains(mention, "/") { mentionOrgAndTeam := strings.Split(mention, "/") if mentionOrgAndTeam[0][1:] == ctx.RenderOptions.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") { - replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), "org", ctx.RenderOptions.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "" /*mention*/)) + link := ctx.RenderHelper.ResolveLink(util.URLJoin("org", ctx.RenderOptions.Metas["org"], "teams", mentionOrgAndTeam[1]), LinkTypeApp) + replaceContent(node, loc.Start, loc.End, createLink(ctx, link, mention, "" /*mention*/)) node = node.NextSibling.NextSibling start = 0 continue @@ -43,8 +44,9 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { } mentionedUsername := mention[1:] - if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx, mentionedUsername) { - replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), mentionedUsername), mention, "" /*mention*/)) + if DefaultRenderHelperFuncs != nil && DefaultRenderHelperFuncs.IsUsernameMentionable(ctx, mentionedUsername) { + link := ctx.RenderHelper.ResolveLink(mentionedUsername, LinkTypeApp) + replaceContent(node, loc.Start, loc.End, createLink(ctx, link, mention, "" /*mention*/)) node = node.NextSibling.NextSibling start = 0 } else { diff --git a/modules/markup/html_node.go b/modules/markup/html_node.go index a7c323fcba8a9..76cdce9245b9d 100644 --- a/modules/markup/html_node.go +++ b/modules/markup/html_node.go @@ -17,7 +17,7 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) { } if IsNonEmptyRelativePath(attr.Val) { - attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val) + attr.Val = util.URLJoin(ctx.RenderHelper.ResolveLink(attr.Val, LinkTypeMedia)) // By default, the "" tag should also be clickable, // because frontend use `` to paste the re-scaled image into the markdown, @@ -53,7 +53,7 @@ func visitNodeVideo(ctx *RenderContext, node *html.Node) (next *html.Node) { continue } if IsNonEmptyRelativePath(attr.Val) { - attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val) + attr.Val = util.URLJoin(ctx.RenderHelper.ResolveLink(attr.Val, LinkTypeMedia)) } attr.Val = camoHandleLink(attr.Val) node.Attr[i] = attr diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 7366965a9d5ce..f82c6b997fdad 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -9,7 +9,6 @@ import ( "testing" "code.gitea.io/gitea/modules/emoji" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" @@ -22,44 +21,13 @@ import ( var ( testRepoOwnerName = "user13" testRepoName = "repo11" - localMetas = map[string]string{ - "user": testRepoOwnerName, - "repo": testRepoName, - } - localWikiMetas = map[string]string{ - "user": testRepoOwnerName, - "repo": testRepoName, - "markupContentMode": "wiki", - } + localMetas = map[string]string{"user": testRepoOwnerName, "repo": testRepoName} ) -type mockRepo struct { - OwnerName string - RepoName string -} - -func (m *mockRepo) GetOwnerName() string { - return m.OwnerName -} - -func (m *mockRepo) GetName() string { - return m.RepoName -} - -func newMockRepo(ownerName, repoName string) gitrepo.Repository { - return &mockRepo{ - OwnerName: ownerName, - RepoName: repoName, - } -} - func TestRender_Commits(t *testing.T) { - setting.AppURL = markup.TestAppURL test := func(input, expected string) { - buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas, newMockRepo(testRepoOwnerName, testRepoName), markup.Links{ - AbsolutePrefix: true, - Base: markup.TestRepoURL, - }), input) + rctx := markup.NewTestRenderContext(markup.TestAppURL, localMetas).WithRelativePath("a.md") + buffer, err := markup.RenderString(rctx, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -102,14 +70,10 @@ func TestRender_Commits(t *testing.T) { } func TestRender_CrossReferences(t *testing.T) { - setting.AppURL = markup.TestAppURL - defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() test := func(input, expected string) { - buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas, - markup.Links{ - AbsolutePrefix: true, - Base: setting.AppSubURL, - }), input) + rctx := markup.NewTestRenderContext(markup.TestAppURL, localMetas).WithRelativePath("a.md") + buffer, err := markup.RenderString(rctx, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -141,9 +105,9 @@ func TestRender_CrossReferences(t *testing.T) { func TestRender_links(t *testing.T) { setting.AppURL = markup.TestAppURL - defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() test := func(input, expected string) { - buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input) + buffer, err := markup.RenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -246,9 +210,9 @@ func TestRender_links(t *testing.T) { func TestRender_email(t *testing.T) { setting.AppURL = markup.TestAppURL - defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)() + defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() test := func(input, expected string) { - res, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input) + res, err := markup.RenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res)) } @@ -315,7 +279,7 @@ func TestRender_emoji(t *testing.T) { test := func(input, expected string) { expected = strings.ReplaceAll(expected, "&", "&") - buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input) + buffer, err := markup.RenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -374,188 +338,166 @@ func TestRender_ShortLinks(t *testing.T) { setting.AppURL = markup.TestAppURL tree := util.URLJoin(markup.TestRepoURL, "src", "master") - test := func(input, expected, expectedWiki string) { - buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: markup.TestRepoURL, BranchPath: "master"}), input) + test := func(input, expected string) { + buffer, err := markdown.RenderString(markup.NewTestRenderContext(tree), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) - buffer, err = markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: markup.TestRepoURL}, localWikiMetas), input) - assert.NoError(t, err) - assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer))) } - mediatree := util.URLJoin(markup.TestRepoURL, "media", "master") url := util.URLJoin(tree, "Link") otherURL := util.URLJoin(tree, "Other-Link") encodedURL := util.URLJoin(tree, "Link%3F") - imgurl := util.URLJoin(mediatree, "Link.jpg") - otherImgurl := util.URLJoin(mediatree, "Link+Other.jpg") - encodedImgurl := util.URLJoin(mediatree, "Link+%23.jpg") - notencodedImgurl := util.URLJoin(mediatree, "some", "path", "Link+#.jpg") - urlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link") - otherURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Other-Link") - encodedURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link%3F") - imgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link.jpg") - otherImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+Other.jpg") - encodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+%23.jpg") - notencodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "some", "path", "Link+#.jpg") + imgurl := util.URLJoin(tree, "Link.jpg") + otherImgurl := util.URLJoin(tree, "Link+Other.jpg") + encodedImgurl := util.URLJoin(tree, "Link+%23.jpg") + notencodedImgurl := util.URLJoin(tree, "some", "path", "Link+#.jpg") renderableFileURL := util.URLJoin(tree, "markdown_file.md") - renderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "markdown_file.md") unrenderableFileURL := util.URLJoin(tree, "file.zip") - unrenderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "file.zip") favicon := "http://google.com/favicon.ico" test( "[[Link]]", ``, - ``) + ) test( "[[Link.-]]", ``, - ``) + ) test( "[[Link.jpg]]", ``, - ``) + ) test( "[["+favicon+"]]", ``, - ``) + ) test( "[[Name|Link]]", ``, - ``) + ) test( "[[Name|Link.jpg]]", ``, - ``) + ) test( "[[Name|Link.jpg|alt=AltName]]", ``, - ``) + ) test( "[[Name|Link.jpg|title=Title]]", ``, - ``) + ) test( "[[Name|Link.jpg|alt=AltName|title=Title]]", ``, - ``) + ) test( "[[Name|Link.jpg|alt=\"AltName\"|title='Title']]", ``, - ``) + ) test( "[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]", ``, - ``) + ) test( "[[Link]] [[Other Link]]", ``, - ``) + ) test( "[[Link?]]", ``, - ``) + ) test( "[[Link]] [[Other Link]] [[Link?]]", ``, - ``) + ) test( "[[markdown_file.md]]", ``, - ``) + ) test( "[[file.zip]]", ``, - ``) + ) test( "[[Link #.jpg]]", ``, - ``) + ) test( "[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]", ``, - ``) + ) test( "[[some/path/Link #.jpg]]", ``, - ``) + ) test( "", ``, - ``) + ) } -func TestRender_RelativeMedias(t *testing.T) { - render := func(input string, isWiki bool, links markup.Links) string { - buffer, err := markdown.RenderString(markup.NewTestRenderContext(links, util.Iif(isWiki, localWikiMetas, localMetas)), input) - assert.NoError(t, err) - return strings.TrimSpace(string(buffer)) - } - - out := render(``, false, markup.Links{Base: "/test-owner/test-repo"}) - assert.Equal(t, ``, out) +/* + func TestRender_RelativeMedias(t *testing.T) { + render := func(input string, isWiki bool, links markup.Links) string { + buffer, err := markdown.RenderString(markup.NewTestRenderContext(links, util.Iif(isWiki, localWikiMetas, localMetas)), input) + assert.NoError(t, err) + return strings.TrimSpace(string(buffer)) + } - out = render(``, true, markup.Links{Base: "/test-owner/test-repo"}) - assert.Equal(t, ``, out) + out := render(``, false, markup.Links{Base: "/test-owner/test-repo"}) + assert.Equal(t, ``, out) - out = render(``, false, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) - assert.Equal(t, ``, out) + out = render(``, true, markup.Links{Base: "/test-owner/test-repo"}) + assert.Equal(t, ``, out) - out = render(``, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) - assert.Equal(t, ``, out) + out = render(``, false, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) + assert.Equal(t, ``, out) - out = render(``, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) - assert.Equal(t, ``, out) + out = render(``, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"}) + assert.Equal(t, ``, out) - out = render(` |