diff options
Diffstat (limited to 'modules/markup')
-rw-r--r-- | modules/markup/external/external.go | 13 | ||||
-rw-r--r-- | modules/markup/html.go | 57 | ||||
-rw-r--r-- | modules/markup/html_internal_test.go | 30 | ||||
-rw-r--r-- | modules/markup/html_test.go | 117 | ||||
-rw-r--r-- | modules/markup/markdown/goldmark.go | 30 | ||||
-rw-r--r-- | modules/markup/markdown/markdown.go | 6 | ||||
-rw-r--r-- | modules/markup/markdown/markdown_test.go | 422 | ||||
-rw-r--r-- | modules/markup/orgmode/orgmode.go | 58 | ||||
-rw-r--r-- | modules/markup/orgmode/orgmode_test.go | 15 | ||||
-rw-r--r-- | modules/markup/renderer.go | 42 |
10 files changed, 596 insertions, 194 deletions
diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index ffbb6da4da..122517ed11 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -79,9 +79,10 @@ func envMark(envName string) string { // Render renders the data of the document to HTML via the external tool. func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { var ( - urlRawPrefix = strings.Replace(ctx.URLPrefix, "/src/", "/raw/", 1) - command = strings.NewReplacer(envMark("GITEA_PREFIX_SRC"), ctx.URLPrefix, - envMark("GITEA_PREFIX_RAW"), urlRawPrefix).Replace(p.Command) + command = strings.NewReplacer( + envMark("GITEA_PREFIX_SRC"), ctx.Links.SrcLink(), + envMark("GITEA_PREFIX_RAW"), ctx.Links.RawLink(), + ).Replace(p.Command) commands = strings.Fields(command) args = commands[1:] ) @@ -121,14 +122,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. ctx.Ctx = graceful.GetManager().ShutdownContext() } - processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix)) + processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.Links.SrcLink())) defer finished() cmd := exec.CommandContext(processCtx, commands[0], args...) cmd.Env = append( os.Environ(), - "GITEA_PREFIX_SRC="+ctx.URLPrefix, - "GITEA_PREFIX_RAW="+urlRawPrefix, + "GITEA_PREFIX_SRC="+ctx.Links.SrcLink(), + "GITEA_PREFIX_RAW="+ctx.Links.RawLink(), ) if !p.IsInputFile { cmd.Stdin = input diff --git a/modules/markup/html.go b/modules/markup/html.go index 05b1c3ef72..a64e4c565d 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -80,15 +80,10 @@ const keywordClass = "issue-keyword" // IsLink reports whether link fits valid format. func IsLink(link []byte) bool { - return isLink(link) -} - -// isLink reports whether link fits valid format. -func isLink(link []byte) bool { return validLinksPattern.Match(link) } -func isLinkStr(link string) bool { +func IsLinkStr(link string) bool { return validLinksPattern.MatchString(link) } @@ -344,7 +339,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output node = node.FirstChild } - visitNode(ctx, procs, procs, node) + visitNode(ctx, procs, node) newNodes := make([]*html.Node, 0, 5) @@ -375,7 +370,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output return nil } -func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node) { +func visitNode(ctx *RenderContext, procs []processor, node *html.Node) { // Add user-content- to IDs and "#" links if they don't already have them for idx, attr := range node.Attr { val := strings.TrimPrefix(attr.Val, "#") @@ -390,35 +385,29 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node } if attr.Key == "class" && attr.Val == "emoji" { - textProcs = nil + procs = nil } } // We ignore code and pre. switch node.Type { case html.TextNode: - textNode(ctx, textProcs, node) + textNode(ctx, procs, node) case html.ElementNode: if node.Data == "img" { for i, attr := range node.Attr { if attr.Key != "src" { continue } - if len(attr.Val) > 0 && !isLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") { - prefix := ctx.URLPrefix - if ctx.IsWiki { - prefix = util.URLJoin(prefix, "wiki", "raw") - } - prefix = strings.Replace(prefix, "/src/", "/media/", 1) - - attr.Val = util.URLJoin(prefix, attr.Val) + if len(attr.Val) > 0 && !IsLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") { + attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val) } attr.Val = camoHandleLink(attr.Val) node.Attr[i] = attr } } else if node.Data == "a" { // Restrict text in links to emojis - textProcs = emojiProcessors + procs = emojiProcessors } else if node.Data == "code" || node.Data == "pre" { return } else if node.Data == "i" { @@ -444,7 +433,7 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node } } for n := node.FirstChild; n != nil; n = n.NextSibling { - visitNode(ctx, procs, textProcs, n) + visitNode(ctx, procs, n) } } // ignore everything else @@ -641,10 +630,6 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { } func shortLinkProcessor(ctx *RenderContext, node *html.Node) { - shortLinkProcessorFull(ctx, node, false) -} - -func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { next := node.NextSibling for node != nil && node != next { m := shortLinkPattern.FindStringSubmatchIndex(node.Data) @@ -665,7 +650,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { if equalPos := strings.IndexByte(v, '='); equalPos == -1 { // There is no equal in this argument; this is a mandatory arg if props["name"] == "" { - if isLinkStr(v) { + if IsLinkStr(v) { // If we clearly see it is a link, we save it so // But first we need to ensure, that if both mandatory args provided @@ -740,7 +725,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { DataAtom: atom.A, } childNode.Parent = linkNode - absoluteLink := isLinkStr(link) + absoluteLink := IsLinkStr(link) if !absoluteLink { if image { link = strings.ReplaceAll(link, " ", "+") @@ -751,16 +736,9 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { link = url.PathEscape(link) } } - urlPrefix := ctx.URLPrefix if image { if !absoluteLink { - if IsSameDomain(urlPrefix) { - urlPrefix = strings.Replace(urlPrefix, "/src/", "/raw/", 1) - } - if ctx.IsWiki { - link = util.URLJoin("wiki", "raw", link) - } - link = util.URLJoin(urlPrefix, link) + link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), link) } title := props["title"] if title == "" { @@ -789,18 +767,15 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { } else { if !absoluteLink { if ctx.IsWiki { - link = util.URLJoin("wiki", link) + link = util.URLJoin(ctx.Links.WikiLink(), link) + } else { + link = util.URLJoin(ctx.Links.SrcLink(), link) } - link = util.URLJoin(urlPrefix, link) } childNode.Type = html.TextNode childNode.Data = name } - if noLink { - linkNode = childNode - } else { - linkNode.Attr = []html.Attribute{{Key: "href", Val: link}} - } + linkNode.Attr = []html.Attribute{{Key: "href", Val: link}} replaceContent(node, m[0], m[1], linkNode) node = node.NextSibling.NextSibling } diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 7b7f6df701..5ba9561915 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -287,8 +287,8 @@ func TestRender_IssueIndexPattern_Document(t *testing.T) { } func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { - if ctx.URLPrefix == "" { - ctx.URLPrefix = TestAppURL + if ctx.Links.Base == "" { + ctx.Links.Base = TestRepoURL } var buf strings.Builder @@ -303,19 +303,23 @@ func TestRender_AutoLink(t *testing.T) { test := func(input, expected string) { var buffer strings.Builder err := PostProcess(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: 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: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, + IsWiki: true, }, strings.NewReader(input), &buffer) assert.Equal(t, err, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) @@ -342,9 +346,11 @@ func TestRender_FullIssueURLs(t *testing.T) { test := func(input, expected string) { var result strings.Builder err := postProcess(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, }, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) assert.NoError(t, err) assert.Equal(t, expected, result.String()) diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 62fd0f5a85..89ecfc036b 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -42,8 +42,10 @@ func TestRender_Commits(t *testing.T) { buffer, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: ".md", - URLPrefix: markup.TestRepoURL, - Metas: localMetas, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -93,8 +95,10 @@ func TestRender_CrossReferences(t *testing.T) { buffer, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: setting.AppSubURL, - Metas: localMetas, + Links: markup.Links{ + Base: setting.AppSubURL, + }, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -138,7 +142,9 @@ func TestRender_links(t *testing.T) { buffer, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: markup.TestRepoURL, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -238,7 +244,9 @@ func TestRender_email(t *testing.T) { res, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: markup.TestRepoURL, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res)) @@ -309,7 +317,9 @@ func TestRender_emoji(t *testing.T) { buffer, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: markup.TestRepoURL, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -371,29 +381,34 @@ func TestRender_ShortLinks(t *testing.T) { test := func(input, expected, expectedWiki string) { buffer, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: tree, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: markup.TestRepoURL, + BranchPath: "master", + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer, err = markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: markup.TestRepoURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, + Metas: localMetas, + IsWiki: true, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) } - rawtree := util.URLJoin(markup.TestRepoURL, "raw", "master") + 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(rawtree, "Link.jpg") - otherImgurl := util.URLJoin(rawtree, "Link+Other.jpg") - encodedImgurl := util.URLJoin(rawtree, "Link+%23.jpg") - notencodedImgurl := util.URLJoin(rawtree, "some", "path", "Link+#.jpg") + 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") @@ -475,21 +490,25 @@ func TestRender_ShortLinks(t *testing.T) { func TestRender_RelativeImages(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.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: tree, - Metas: localMetas, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: markup.TestRepoURL, + BranchPath: "master", + }, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer, err = markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: markup.TestRepoURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, + Metas: localMetas, + IsWiki: true, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) @@ -521,9 +540,11 @@ func Test_ParseClusterFuzz(t *testing.T) { var res strings.Builder err := markup.PostProcess(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: "https://example.com", - Metas: localMetas, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: "https://example.com", + }, + Metas: localMetas, }, strings.NewReader(data), &res) assert.NoError(t, err) assert.NotContains(t, res.String(), "<html") @@ -532,9 +553,11 @@ func Test_ParseClusterFuzz(t *testing.T) { res.Reset() err = markup.PostProcess(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: "https://example.com", - Metas: localMetas, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: "https://example.com", + }, + Metas: localMetas, }, strings.NewReader(data), &res) assert.NoError(t, err) @@ -543,6 +566,7 @@ func Test_ParseClusterFuzz(t *testing.T) { func TestPostProcess_RenderDocument(t *testing.T) { setting.AppURL = markup.TestAppURL + setting.StaticURLPrefix = markup.TestAppURL // can't run standalone localMetas := map[string]string{ "user": "go-gitea", @@ -553,9 +577,11 @@ func TestPostProcess_RenderDocument(t *testing.T) { test := func(input, expected string) { var res strings.Builder err := markup.PostProcess(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: "https://example.com", - Metas: localMetas, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: "https://example.com", + }, + Metas: localMetas, }, strings.NewReader(input), &res) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String())) @@ -594,9 +620,8 @@ func TestIssue16020(t *testing.T) { var res strings.Builder err := markup.PostProcess(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: "https://example.com", - Metas: localMetas, + Ctx: git.DefaultContext, + Metas: localMetas, }, strings.NewReader(data), &res) assert.NoError(t, err) assert.Equal(t, data, res.String()) @@ -611,9 +636,8 @@ func BenchmarkEmojiPostprocess(b *testing.B) { for i := 0; i < b.N; i++ { var res strings.Builder err := markup.PostProcess(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: "https://example.com", - Metas: localMetas, + Ctx: git.DefaultContext, + Metas: localMetas, }, strings.NewReader(data), &res) assert.NoError(b, err) } @@ -622,8 +646,10 @@ func BenchmarkEmojiPostprocess(b *testing.B) { func TestFuzz(t *testing.T) { s := "t/l/issues/8#/../../a" renderContext := markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: "https://example.com/go-gitea/gitea", + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: "https://example.com/go-gitea/gitea", + }, Metas: map[string]string{ "user": "go-gitea", "repo": "gitea", @@ -640,9 +666,8 @@ func TestIssue18471(t *testing.T) { var res strings.Builder err := markup.PostProcess(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: "https://example.com", - Metas: localMetas, + Ctx: git.DefaultContext, + Metas: localMetas, }, strings.NewReader(data), &res) assert.NoError(t, err) diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index ff4e6b1bd0..b92b90561b 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -87,18 +87,8 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa // Check if the destination is a real link link := v.Destination if len(link) > 0 && !markup.IsLink(link) { - prefix := pc.Get(urlPrefixKey).(string) - if pc.Get(isWikiKey).(bool) { - prefix = giteautil.URLJoin(prefix, "wiki", "raw") - } - prefix = strings.Replace(prefix, "/src/", "/media/", 1) - - lnk := strings.TrimLeft(string(link), "/") - - lnk = giteautil.URLJoin(prefix, lnk) - link = []byte(lnk) + v.Destination = []byte(giteautil.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), string(link))) } - v.Destination = link parent := n.Parent() // Create a link around image only if parent is not already a link @@ -107,7 +97,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa // Create a link wrapper wrap := ast.NewLink() - wrap.Destination = link + wrap.Destination = v.Destination wrap.Title = v.Title wrap.SetAttributeString("target", []byte("_blank")) @@ -143,11 +133,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa link[0] != '#' && !bytes.HasPrefix(link, byteMailto) { // special case: this is not a link, a hash link or a mailto:, so it's a // relative URL - lnk := string(link) - if pc.Get(isWikiKey).(bool) { - lnk = giteautil.URLJoin("wiki", lnk) + + var base string + if ctx.IsWiki { + base = ctx.Links.WikiLink() + } else { + base = ctx.Links.Base } - link = []byte(giteautil.URLJoin(pc.Get(urlPrefixKey).(string), lnk)) + + link = []byte(giteautil.URLJoin(base, string(link))) } if len(link) > 0 && link[0] == '#' { link = []byte("#user-content-" + string(link)[1:]) @@ -188,9 +182,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa applyElementDir(v) case *ast.Text: if v.SoftLineBreak() && !v.HardLineBreak() { - renderMetas := pc.Get(renderMetasKey).(map[string]string) - mode := renderMetas["mode"] - if mode != "document" { + if ctx.Metas["mode"] != "document" { v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments) } else { v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 43885889d1..771162b9a3 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -34,9 +34,6 @@ var ( ) var ( - urlPrefixKey = parser.NewContextKey() - isWikiKey = parser.NewContextKey() - renderMetasKey = parser.NewContextKey() renderContextKey = parser.NewContextKey() renderConfigKey = parser.NewContextKey() ) @@ -66,9 +63,6 @@ func (l *limitWriter) Write(data []byte) (int, error) { // newParserContext creates a parser.Context with the render context set func newParserContext(ctx *markup.RenderContext) parser.Context { pc := parser.NewContext(parser.WithIDs(newPrefixedIDs())) - pc.Set(urlPrefixKey, ctx.URLPrefix) - pc.Set(isWikiKey, ctx.IsWiki) - pc.Set(renderMetasKey, ctx.Metas) pc.Set(renderContextKey, ctx) return pc } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 8f855f1b13..957d773acd 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -52,16 +52,20 @@ func TestRender_StandardLinks(t *testing.T) { test := func(input, expected, expectedWiki string) { buffer, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer, err = markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, + IsWiki: true, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) @@ -83,8 +87,10 @@ func TestRender_Images(t *testing.T) { test := func(input, expected string) { buffer, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -107,7 +113,6 @@ func TestRender_Images(t *testing.T) { "[!["+title+"]("+url+")]("+href+")", `<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`) - url = "/../../.images/src/02/train.jpg" test( "!["+title+"]("+url+")", `<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`) @@ -286,14 +291,16 @@ func TestTotal_RenderWiki(t *testing.T) { setting.AppURL = AppURL setting.AppSubURL = AppSubURL - answers := testAnswers(util.URLJoin(AppSubURL, "wiki/"), util.URLJoin(AppSubURL, "wiki", "raw/")) + answers := testAnswers(util.URLJoin(AppSubURL, "wiki"), util.URLJoin(AppSubURL, "wiki", "raw")) for i := 0; i < len(sameCases); i++ { line, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: AppSubURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, + Metas: localMetas, + IsWiki: true, }, sameCases[i]) assert.NoError(t, err) assert.Equal(t, answers[i], line) @@ -314,9 +321,11 @@ func TestTotal_RenderWiki(t *testing.T) { for i := 0; i < len(testCases); i += 2 { line, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: AppSubURL, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, + IsWiki: true, }, testCases[i]) assert.NoError(t, err) assert.Equal(t, testCases[i+1], line) @@ -327,13 +336,16 @@ func TestTotal_RenderString(t *testing.T) { setting.AppURL = AppURL setting.AppSubURL = AppSubURL - answers := testAnswers(util.URLJoin(AppSubURL, "src", "master/"), util.URLJoin(AppSubURL, "raw", "master/")) + answers := testAnswers(util.URLJoin(AppSubURL, "src", "master"), util.URLJoin(AppSubURL, "media", "master")) for i := 0; i < len(sameCases); i++ { line, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: util.URLJoin(AppSubURL, "src", "master/"), - Metas: localMetas, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: AppSubURL, + BranchPath: "master", + }, + Metas: localMetas, }, sameCases[i]) assert.NoError(t, err) assert.Equal(t, answers[i], line) @@ -343,8 +355,10 @@ func TestTotal_RenderString(t *testing.T) { for i := 0; i < len(testCases); i += 2 { line, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: AppSubURL, + }, }, testCases[i]) assert.NoError(t, err) assert.Equal(t, testCases[i+1], line) @@ -556,3 +570,367 @@ foo: bar assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase) } } + +func TestRenderLinks(t *testing.T) { + input := ` space @mention-user +/just/a/path.bin +https://example.com/file.bin +[local link](file.bin) +[remote link](https://example.com) +[[local link|file.bin]] +[[remote link|https://example.com]] +![local image](image.jpg) +![remote image](https://example.com/image.jpg) +[[local image|image.jpg]] +[[remote link|https://example.com/image.jpg]] +https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare +https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit +:+1: +mail@domain.com +@mention-user test +#123 + space +` + cases := []struct { + Links markup.Links + IsWiki bool + Expected string + }{ + { // 0 + Links: markup.Links{}, + IsWiki: false, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/src/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 1 + Links: markup.Links{}, + IsWiki: true, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/wiki/raw/image.jpg" rel="nofollow"><img src="/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 2 + Links: markup.Links{ + Base: "https://gitea.io/", + }, + IsWiki: false, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="https://gitea.io/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="https://gitea.io/src/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="https://gitea.io/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="https://gitea.io/image.jpg" rel="nofollow"><img src="https://gitea.io/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 3 + Links: markup.Links{ + Base: "https://gitea.io/", + }, + IsWiki: true, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="https://gitea.io/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="https://gitea.io/wiki/raw/image.jpg" rel="nofollow"><img src="https://gitea.io/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 4 + Links: markup.Links{ + Base: "/relative/path", + }, + IsWiki: false, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/relative/path/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/src/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/relative/path/image.jpg" rel="nofollow"><img src="/relative/path/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 5 + Links: markup.Links{ + Base: "/relative/path", + }, + IsWiki: true, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 6 + Links: markup.Links{ + Base: "/user/repo", + BranchPath: "branch/main", + }, + IsWiki: false, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/user/repo/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/user/repo/src/branch/main/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/user/repo/media/branch/main/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/user/repo/media/branch/main/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 7 + Links: markup.Links{ + Base: "/relative/path", + BranchPath: "branch/main", + }, + IsWiki: true, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 8 + Links: markup.Links{ + Base: "/user/repo", + TreePath: "sub/folder", + }, + IsWiki: false, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/user/repo/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/user/repo/src/sub/folder/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/user/repo/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/user/repo/image.jpg" rel="nofollow"><img src="/user/repo/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 9 + Links: markup.Links{ + Base: "/relative/path", + TreePath: "sub/folder", + }, + IsWiki: true, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 10 + Links: markup.Links{ + Base: "/user/repo", + BranchPath: "branch/main", + TreePath: "sub/folder", + }, + IsWiki: false, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/user/repo/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/user/repo/src/branch/main/sub/folder/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/user/repo/media/branch/main/sub/folder/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/user/repo/media/branch/main/sub/folder/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + { // 11 + Links: markup.Links{ + Base: "/relative/path", + BranchPath: "branch/main", + TreePath: "sub/folder", + }, + IsWiki: true, + Expected: `<p>space @mention-user<br/> +/just/a/path.bin<br/> +<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/> +<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/> +<a href="https://example.com" rel="nofollow">remote link</a><br/> +<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/> +<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/> +<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/> +<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> +<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> +<span class="emoji" aria-label="thumbs up">👍</span><br/> +<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> +@mention-user test<br/> +#123<br/> +space</p> +`, + }, + } + + for i, c := range cases { + result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input) + assert.NoError(t, err, "Unexpected error in testcase: %v", i) + assert.Equal(t, c.Expected, result, "Unexpected result in testcase %v", i) + } +} diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index e7af02b496..abc641fbe2 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -4,7 +4,6 @@ package markup import ( - "bytes" "fmt" "html" "io" @@ -101,8 +100,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error w := &Writer{ HTMLWriter: htmlWriter, - URLPrefix: ctx.URLPrefix, - IsWiki: ctx.IsWiki, + Ctx: ctx, } htmlWriter.ExtendingWriter = w @@ -132,63 +130,53 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri // Writer implements org.Writer type Writer struct { *org.HTMLWriter - URLPrefix string - IsWiki bool + Ctx *markup.RenderContext } -var byteMailto = []byte("mailto:") +const mailto = "mailto:" -// WriteRegularLink renders images, links or videos -func (r *Writer) WriteRegularLink(l org.RegularLink) { - link := []byte(html.EscapeString(l.URL)) +func (r *Writer) resolveLink(l org.RegularLink) string { + link := html.EscapeString(l.URL) if l.Protocol == "file" { link = link[len("file:"):] } - if len(link) > 0 && !markup.IsLink(link) && - link[0] != '#' && !bytes.HasPrefix(link, byteMailto) { - lnk := string(link) - if r.IsWiki { - lnk = util.URLJoin("wiki", lnk) + if len(link) > 0 && !markup.IsLinkStr(link) && + link[0] != '#' && !strings.HasPrefix(link, mailto) { + base := r.Ctx.Links.Base + switch l.Kind() { + case "image", "video": + base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki) } - link = []byte(util.URLJoin(r.URLPrefix, lnk)) + link = util.URLJoin(base, link) } + return link +} + +// WriteRegularLink renders images, links or videos +func (r *Writer) WriteRegularLink(l org.RegularLink) { + link := r.resolveLink(l) // Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427 switch l.Kind() { case "image": if l.Description == nil { - imageSrc := getMediaURL(link) - fmt.Fprintf(r, `<img src="%s" alt="%s" />`, imageSrc, link) + fmt.Fprintf(r, `<img src="%s" alt="%s" />`, link, link) } else { - description := strings.TrimPrefix(org.String(l.Description...), "file:") - imageSrc := getMediaURL([]byte(description)) + imageSrc := r.resolveLink(l.Description[0].(org.RegularLink)) fmt.Fprintf(r, `<a href="%s"><img src="%s" alt="%s" /></a>`, link, imageSrc, imageSrc) } case "video": if l.Description == nil { - imageSrc := getMediaURL(link) - fmt.Fprintf(r, `<video src="%s">%s</video>`, imageSrc, link) + fmt.Fprintf(r, `<video src="%s">%s</video>`, link, link) } else { - description := strings.TrimPrefix(org.String(l.Description...), "file:") - videoSrc := getMediaURL([]byte(description)) + videoSrc := r.resolveLink(l.Description[0].(org.RegularLink)) fmt.Fprintf(r, `<a href="%s"><video src="%s">%s</video></a>`, link, videoSrc, videoSrc) } default: - description := string(link) + description := link if l.Description != nil { description = r.WriteNodesAsString(l.Description...) } fmt.Fprintf(r, `<a href="%s">%s</a>`, link, description) } } - -func getMediaURL(l []byte) string { - srcURL := string(l) - - // Check if link is valid - if len(srcURL) > 0 && !markup.IsLink(l) { - srcURL = strings.Replace(srcURL, "/src/", "/media/", 1) - } - - return srcURL -} diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go index 88ae14ebcf..abf5ca8fcf 100644 --- a/modules/markup/orgmode/orgmode_test.go +++ b/modules/markup/orgmode/orgmode_test.go @@ -27,8 +27,10 @@ func TestRender_StandardLinks(t *testing.T) { test := func(input, expected string) { buffer, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -48,8 +50,10 @@ func TestRender_Media(t *testing.T) { test := func(input, expected string) { buffer, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -84,8 +88,7 @@ func TestRender_Source(t *testing.T) { test := func(input, expected string) { buffer, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 0331c3742a..5a7adcc553 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/yuin/goldmark/ast" ) @@ -69,7 +70,7 @@ type RenderContext struct { RelativePath string // relative path from tree root of the branch Type string IsWiki bool - URLPrefix string + Links Links Metas map[string]string DefaultLink string GitRepo *git.Repository @@ -80,6 +81,45 @@ type RenderContext struct { InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page } +type Links struct { + Base string + BranchPath string + TreePath string +} + +func (l *Links) HasBranchInfo() bool { + return l.BranchPath != "" +} + +func (l *Links) SrcLink() string { + return util.URLJoin(l.Base, "src", l.BranchPath, l.TreePath) +} + +func (l *Links) MediaLink() string { + return util.URLJoin(l.Base, "media", l.BranchPath, l.TreePath) +} + +func (l *Links) RawLink() string { + return util.URLJoin(l.Base, "raw", l.BranchPath, l.TreePath) +} + +func (l *Links) WikiLink() string { + return util.URLJoin(l.Base, "wiki") +} + +func (l *Links) WikiRawLink() string { + return util.URLJoin(l.Base, "wiki/raw") +} + +func (l *Links) ResolveMediaLink(isWiki bool) string { + if isWiki { + return l.WikiRawLink() + } else if l.HasBranchInfo() { + return l.MediaLink() + } + return l.Base +} + // Cancel runs any cleanup functions that have been registered for this Ctx func (ctx *RenderContext) Cancel() { if ctx == nil { |