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]]",
`
`,
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
@@ -691,7 +691,7 @@ space
https://example.com/file.bin
local link
remote link
-local link
+local link
remote link
@@ -749,7 +749,7 @@ space
https://example.com/file.bin
local link
remote link
-local link
+local link
remote link
@@ -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
@@ -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)
}