From 21783a5752f518e579d8fe48b33504a16674ee17 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 18 Jun 2024 11:09:20 +0800 Subject: [PATCH] Fix rendered wiki page link (#31398) Fix #31395 --- modules/markup/html.go | 27 ++--------------- modules/markup/html_link.go | 35 +++++++++++++++++++++++ modules/markup/html_test.go | 4 +++ modules/markup/markdown/markdown_test.go | 10 +++---- modules/markup/markdown/transform_link.go | 29 ++----------------- modules/markup/renderer.go | 12 ++------ modules/templates/util_render_test.go | 4 +-- routers/web/repo/render.go | 2 +- routers/web/repo/view.go | 6 ++-- routers/web/repo/wiki.go | 2 +- 10 files changed, 58 insertions(+), 73 deletions(-) create mode 100644 modules/markup/html_link.go diff --git a/modules/markup/html.go b/modules/markup/html.go index b3a241af7a3e6..c312d3cba0e50 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -748,10 +748,10 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { if image { link = strings.ReplaceAll(link, " ", "+") } else { - link = strings.ReplaceAll(link, " ", "-") + link = strings.ReplaceAll(link, " ", "-") // FIXME: it should support dashes in the link, eg: "the-dash-support.-" } if !strings.Contains(link, "/") { - link = url.PathEscape(link) + link = url.PathEscape(link) // FIXME: it doesn't seem right and it might cause double-escaping } } if image { @@ -783,28 +783,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { childNode.Attr = childNode.Attr[:2] } } else { - if !absoluteLink { - var base string - if ctx.IsWiki { - switch ext { - case "": - // no file extension, create a regular wiki link - base = ctx.Links.WikiLink() - default: - // we have a file extension: - // return a regular wiki link if it's a renderable file (extension), - // raw link otherwise - if Type(link) != "" { - base = ctx.Links.WikiLink() - } else { - base = ctx.Links.WikiRawLink() - } - } - } else { - base = ctx.Links.SrcLink() - } - link = util.URLJoin(base, link) - } + link, _ = ResolveLink(ctx, link, "") childNode.Type = html.TextNode childNode.Data = name } diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go new file mode 100644 index 0000000000000..a41b87e9fa83d --- /dev/null +++ b/modules/markup/html_link.go @@ -0,0 +1,35 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markup + +import ( + "path" + + "code.gitea.io/gitea/modules/util" +) + +func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) { + isAnchorFragment := link != "" && link[0] == '#' + if !isAnchorFragment && !IsFullURLString(link) { + linkBase := ctx.Links.Base + if ctx.IsWiki { + if ext := path.Ext(link); ext == "" || ext == ".-" { + linkBase = ctx.Links.WikiLink() // the link is for a wiki page + } else if DetectMarkupTypeByFileName(link) != "" { + linkBase = ctx.Links.WikiLink() // the link is renderable as a wiki page + } else { + linkBase = ctx.Links.WikiRawLink() // otherwise, use a raw link instead to view&download medias + } + } else if ctx.Links.BranchPath != "" || ctx.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.Links.SrcLink() + } + link, resolved = util.URLJoin(linkBase, link), true + } + if isAnchorFragment && userContentAnchorPrefix != "" { + link, resolved = userContentAnchorPrefix+link[1:], true + } + return link, resolved +} diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index aeb619e12d581..399488912eefe 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -444,6 +444,10 @@ func TestRender_ShortLinks(t *testing.T) { "[[Link]]", `

Link

`, `

Link

`) + test( + "[[Link.-]]", + `

Link.-

`, + `

Link.-

`) test( "[[Link.jpg]]", `

Link.jpg

`, diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 8c41ec12e3fb1..9a8c39df0a2dc 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -635,7 +635,7 @@ mail@domain.com https://example.com/file.bin
local link
remote link
-local link
+local link
remote link
local image
local image
@@ -691,7 +691,7 @@ space

https://example.com/file.bin
local link
remote link
-local link
+local link
remote link
local image
local image
@@ -749,7 +749,7 @@ space

https://example.com/file.bin
local link
remote link
-local link
+local link
remote link
local image
local image
@@ -866,7 +866,7 @@ space

Expected: `

space @mention-user
/just/a/path.bin
https://example.com/file.bin
-local link
+local link
remote link
local link
remote link
@@ -984,7 +984,7 @@ space

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, template.HTML(c.Expected), result, "Unexpected result in testcase %v", i) + assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i) } } diff --git a/modules/markup/markdown/transform_link.go b/modules/markup/markdown/transform_link.go index 527a5dfc44659..38fbf693ab837 100644 --- a/modules/markup/markdown/transform_link.go +++ b/modules/markup/markdown/transform_link.go @@ -4,38 +4,13 @@ package markdown import ( - "path/filepath" - "code.gitea.io/gitea/modules/markup" - giteautil "code.gitea.io/gitea/modules/util" "github.com/yuin/goldmark/ast" ) func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link) { - // Links need their href to munged to be a real value - link := v.Destination - isAnchorFragment := len(link) > 0 && link[0] == '#' - if !isAnchorFragment && !markup.IsFullURLBytes(link) { - base := ctx.Links.Base - if ctx.IsWiki { - if filepath.Ext(string(link)) == "" { - // This link doesn't have a file extension - assume a regular wiki link - base = ctx.Links.WikiLink() - } else if markup.Type(string(link)) != "" { - // If it's a file type we can render, use a regular wiki link - base = ctx.Links.WikiLink() - } else { - // Otherwise, use a raw link instead - base = ctx.Links.WikiRawLink() - } - } else if ctx.Links.HasBranchInfo() { - base = ctx.Links.SrcLink() - } - link = []byte(giteautil.URLJoin(base, string(link))) - } - if isAnchorFragment { - link = []byte("#user-content-" + string(link)[1:]) + if link, resolved := markup.ResolveLink(ctx, string(v.Destination), "#user-content-"); resolved { + v.Destination = []byte(link) } - v.Destination = link } diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 5eb568cb1f0dd..18bdfc9761126 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -372,22 +372,14 @@ func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error { return ErrUnsupportedRenderExtension{extension} } -// Type returns if markup format via the filename -func Type(filename string) string { +// DetectMarkupTypeByFileName returns the possible markup format type via the filename +func DetectMarkupTypeByFileName(filename string) string { if parser := GetRendererByFileName(filename); parser != nil { return parser.Name() } return "" } -// IsMarkupFile reports whether file is a markup type file -func IsMarkupFile(name, markup string) bool { - if parser := GetRendererByFileName(name); parser != nil { - return parser.Name() == markup - } - return false -} - func PreviewableExtensions() []string { extensions := make([]string, 0, len(extRenderers)) for extension := range extRenderers { diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index f493b899e393c..32e53b5215574 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -174,7 +174,7 @@ func TestRenderMarkdownToHtml(t *testing.T) { https://example.com/file.bin local link remote link -local link +local link remote link local image remote image @@ -190,7 +190,7 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit #123 space

` - assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput())) + assert.Equal(t, expected, string(RenderMarkdownToHtml(context.Background(), testInput()))) } func TestRenderLabels(t *testing.T) { diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go index e64db03e2015e..6aba9e0ac1436 100644 --- a/routers/web/repo/render.go +++ b/routers/web/repo/render.go @@ -47,7 +47,7 @@ func RenderFile(ctx *context.Context) { rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{}) ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts") - if markupType := markup.Type(blob.Name()); markupType == "" { + if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType == "" { if isTextFile { _, _ = io.Copy(ctx.Resp, rd) } else { diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 0aa3fe1efd77b..2c478abacf4d2 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -307,7 +307,7 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{}) - if markupType := markup.Type(readmeFile.Name()); markupType != "" { + if markupType := markup.DetectMarkupTypeByFileName(readmeFile.Name()); markupType != "" { ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType @@ -499,7 +499,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { readmeExist := util.IsReadmeFileName(blob.Name()) ctx.Data["ReadmeExist"] = readmeExist - markupType := markup.Type(blob.Name()) + markupType := markup.DetectMarkupTypeByFileName(blob.Name()) // If the markup is detected by custom markup renderer it should not be reset later on // to not pass it down to the render context. detected := false @@ -607,7 +607,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { // TODO: this logic duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go" // It is used by "external renders", markupRender will execute external programs to get rendered content. - if markupType := markup.Type(blob.Name()); markupType != "" { + if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType != "" { rd := io.MultiReader(bytes.NewReader(buf), dataRc) ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index df15f61b173ff..ff6397cd2a82e 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -532,7 +532,7 @@ func Wiki(ctx *context.Context) { } wikiPath := entry.Name() - if markup.Type(wikiPath) != markdown.MarkupName { + if markup.DetectMarkupTypeByFileName(wikiPath) != markdown.MarkupName { ext := strings.ToUpper(filepath.Ext(wikiPath)) ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext) }