mirror of
https://github.com/go-gitea/gitea
synced 2024-11-19 16:44:25 +00:00
parent
e8e43a7ee4
commit
042e9fcd81
@ -761,10 +761,10 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
if image {
|
if image {
|
||||||
link = strings.ReplaceAll(link, " ", "+")
|
link = strings.ReplaceAll(link, " ", "+")
|
||||||
} else {
|
} 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, "/") {
|
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 {
|
if image {
|
||||||
@ -796,28 +796,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
childNode.Attr = childNode.Attr[:2]
|
childNode.Attr = childNode.Attr[:2]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !absoluteLink {
|
link, _ = ResolveLink(ctx, link, "")
|
||||||
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)
|
|
||||||
}
|
|
||||||
childNode.Type = html.TextNode
|
childNode.Type = html.TextNode
|
||||||
childNode.Data = name
|
childNode.Data = name
|
||||||
}
|
}
|
||||||
|
35
modules/markup/html_link.go
Normal file
35
modules/markup/html_link.go
Normal file
@ -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
|
||||||
|
}
|
@ -442,6 +442,10 @@ func TestRender_ShortLinks(t *testing.T) {
|
|||||||
"[[Link]]",
|
"[[Link]]",
|
||||||
`<p><a href="`+url+`" rel="nofollow">Link</a></p>`,
|
`<p><a href="`+url+`" rel="nofollow">Link</a></p>`,
|
||||||
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a></p>`)
|
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a></p>`)
|
||||||
|
test(
|
||||||
|
"[[Link.-]]",
|
||||||
|
`<p><a href="http://localhost:3000/test-owner/test-repo/src/master/Link.-" rel="nofollow">Link.-</a></p>`,
|
||||||
|
`<p><a href="http://localhost:3000/test-owner/test-repo/wiki/Link.-" rel="nofollow">Link.-</a></p>`)
|
||||||
test(
|
test(
|
||||||
"[[Link.jpg]]",
|
"[[Link.jpg]]",
|
||||||
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Link.jpg" alt="Link.jpg"/></a></p>`,
|
`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Link.jpg" alt="Link.jpg"/></a></p>`,
|
||||||
|
@ -67,7 +67,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||||||
case *ast.Image:
|
case *ast.Image:
|
||||||
g.transformImage(ctx, v, reader)
|
g.transformImage(ctx, v, reader)
|
||||||
case *ast.Link:
|
case *ast.Link:
|
||||||
g.transformLink(ctx, v, reader)
|
g.transformLink(ctx, v)
|
||||||
case *ast.List:
|
case *ast.List:
|
||||||
g.transformList(ctx, v, reader, rc)
|
g.transformList(ctx, v, reader, rc)
|
||||||
case *ast.Text:
|
case *ast.Text:
|
||||||
|
@ -626,7 +626,7 @@ mail@domain.com
|
|||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><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="/file.bin" rel="nofollow">local link</a><br/>
|
||||||
<a href="https://example.com" rel="nofollow">remote 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="/file.bin" rel="nofollow">local link</a><br/>
|
||||||
<a href="https://example.com" rel="nofollow">remote 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="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a><br/>
|
||||||
<a href="/path/file" target="_blank" rel="nofollow noopener"><img src="/path/file" alt="local image"/></a><br/>
|
<a href="/path/file" target="_blank" rel="nofollow noopener"><img src="/path/file" alt="local image"/></a><br/>
|
||||||
@ -682,7 +682,7 @@ space</p>
|
|||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><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://gitea.io/file.bin" rel="nofollow">local link</a><br/>
|
||||||
<a href="https://example.com" rel="nofollow">remote 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://gitea.io/file.bin" rel="nofollow">local link</a><br/>
|
||||||
<a href="https://example.com" rel="nofollow">remote 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://gitea.io/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/image.jpg" alt="local image"/></a><br/>
|
||||||
<a href="https://gitea.io/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/path/file" alt="local image"/></a><br/>
|
<a href="https://gitea.io/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/path/file" alt="local image"/></a><br/>
|
||||||
@ -740,7 +740,7 @@ space</p>
|
|||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><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="/relative/path/file.bin" rel="nofollow">local link</a><br/>
|
||||||
<a href="https://example.com" rel="nofollow">remote 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="/relative/path/file.bin" rel="nofollow">local link</a><br/>
|
||||||
<a href="https://example.com" rel="nofollow">remote 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="/relative/path/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/image.jpg" alt="local image"/></a><br/>
|
||||||
<a href="/relative/path/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/path/file" alt="local image"/></a><br/>
|
<a href="/relative/path/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/path/file" alt="local image"/></a><br/>
|
||||||
@ -857,7 +857,7 @@ space</p>
|
|||||||
Expected: `<p>space @mention-user<br/>
|
Expected: `<p>space @mention-user<br/>
|
||||||
/just/a/path.bin<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://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="/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="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="/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="https://example.com" rel="nofollow">remote link</a><br/>
|
||||||
@ -975,7 +975,7 @@ space</p>
|
|||||||
for i, c := range cases {
|
for i, c := range cases {
|
||||||
result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input)
|
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.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,39 +4,13 @@
|
|||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
giteautil "code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/text"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link, reader text.Reader) {
|
func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link) {
|
||||||
// Links need their href to munged to be a real value
|
if link, resolved := markup.ResolveLink(ctx, string(v.Destination), "#user-content-"); resolved {
|
||||||
link := v.Destination
|
v.Destination = []byte(link)
|
||||||
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:])
|
|
||||||
}
|
|
||||||
v.Destination = link
|
|
||||||
}
|
}
|
||||||
|
@ -370,22 +370,14 @@ func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error {
|
|||||||
return ErrUnsupportedRenderExtension{extension}
|
return ErrUnsupportedRenderExtension{extension}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns if markup format via the filename
|
// DetectMarkupTypeByFileName returns the possible markup format type via the filename
|
||||||
func Type(filename string) string {
|
func DetectMarkupTypeByFileName(filename string) string {
|
||||||
if parser := GetRendererByFileName(filename); parser != nil {
|
if parser := GetRendererByFileName(filename); parser != nil {
|
||||||
return parser.Name()
|
return parser.Name()
|
||||||
}
|
}
|
||||||
return ""
|
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 {
|
func PreviewableExtensions() []string {
|
||||||
extensions := make([]string, 0, len(extRenderers))
|
extensions := make([]string, 0, len(extRenderers))
|
||||||
for extension := range extRenderers {
|
for extension := range extRenderers {
|
||||||
|
@ -174,7 +174,7 @@ func TestRenderMarkdownToHtml(t *testing.T) {
|
|||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
||||||
<a href="/file.bin" rel="nofollow">local link</a>
|
<a href="/file.bin" rel="nofollow">local link</a>
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a>
|
<a href="https://example.com" rel="nofollow">remote link</a>
|
||||||
<a href="/src/file.bin" rel="nofollow">local link</a>
|
<a href="/file.bin" rel="nofollow">local link</a>
|
||||||
<a href="https://example.com" rel="nofollow">remote link</a>
|
<a href="https://example.com" rel="nofollow">remote link</a>
|
||||||
<a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a>
|
<a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a>
|
||||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a>
|
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a>
|
||||||
@ -190,7 +190,7 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|||||||
#123
|
#123
|
||||||
space</p>
|
space</p>
|
||||||
`
|
`
|
||||||
assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput()))
|
assert.Equal(t, expected, string(RenderMarkdownToHtml(context.Background(), testInput())))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderLabels(t *testing.T) {
|
func TestRenderLabels(t *testing.T) {
|
||||||
|
@ -47,7 +47,7 @@ func RenderFile(ctx *context.Context) {
|
|||||||
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||||
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
|
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 {
|
if isTextFile {
|
||||||
_, _ = io.Copy(ctx.Resp, rd)
|
_, _ = io.Copy(ctx.Resp, rd)
|
||||||
} else {
|
} else {
|
||||||
|
@ -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{})
|
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["IsMarkup"] = true
|
||||||
ctx.Data["MarkupType"] = markupType
|
ctx.Data["MarkupType"] = markupType
|
||||||
|
|
||||||
@ -499,7 +499,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
readmeExist := util.IsReadmeFileName(blob.Name())
|
readmeExist := util.IsReadmeFileName(blob.Name())
|
||||||
ctx.Data["ReadmeExist"] = readmeExist
|
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
|
// 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.
|
// to not pass it down to the render context.
|
||||||
detected := false
|
detected := false
|
||||||
@ -606,9 +606,9 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this logic seems strange, it duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
|
// TODO: this logic duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
|
||||||
// maybe for this case, the file is a binary file, and shouldn't be rendered?
|
// 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)
|
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
||||||
ctx.Data["IsMarkup"] = true
|
ctx.Data["IsMarkup"] = true
|
||||||
ctx.Data["MarkupType"] = markupType
|
ctx.Data["MarkupType"] = markupType
|
||||||
|
@ -532,7 +532,7 @@ func Wiki(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wikiPath := entry.Name()
|
wikiPath := entry.Name()
|
||||||
if markup.Type(wikiPath) != markdown.MarkupName {
|
if markup.DetectMarkupTypeByFileName(wikiPath) != markdown.MarkupName {
|
||||||
ext := strings.ToUpper(filepath.Ext(wikiPath))
|
ext := strings.ToUpper(filepath.Ext(wikiPath))
|
||||||
ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext)
|
ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user