mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	| @@ -748,10 +748,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 { | ||||||
| @@ -783,28 +783,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 | ||||||
|  | } | ||||||
| @@ -444,6 +444,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>`, | ||||||
|   | |||||||
| @@ -635,7 +635,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/> | ||||||
| @@ -691,7 +691,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/> | ||||||
| @@ -749,7 +749,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/> | ||||||
| @@ -866,7 +866,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/> | ||||||
| @@ -984,7 +984,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,38 +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" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link) { | 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 |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -372,22 +372,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 | ||||||
| @@ -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" | 		// 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. | 		// 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) | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user