mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Fix raw wiki links (#31825)
Fix #31395 This regression is introduced by #30273. To find out how GitHub handles this case, I did [some tests](https://github.com/go-gitea/gitea/issues/31395#issuecomment-2278929115). I use redirect in this PR instead of checking if the corresponding `.md` file exists when rendering the link because GitHub also uses redirect. With this PR, there is no need to resolve the raw wiki link when rendering a wiki page. If a wiki link points to a raw file, access will be redirected to the raw link.
This commit is contained in:
		| @@ -4,8 +4,6 @@ | ||||
| package markup | ||||
|  | ||||
| import ( | ||||
| 	"path" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| ) | ||||
|  | ||||
| @@ -14,13 +12,9 @@ func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (resu | ||||
| 	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 | ||||
| 			} | ||||
| 			// no need to check if the link should be resolved as a wiki link or a wiki raw link | ||||
| 			// just use wiki link here and it will be redirected to a wiki raw link if necessary | ||||
| 			linkBase = ctx.Links.WikiLink() | ||||
| 		} 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}" | ||||
|   | ||||
| @@ -437,7 +437,7 @@ func TestRender_ShortLinks(t *testing.T) { | ||||
| 	renderableFileURL := util.URLJoin(tree, "markdown_file.md") | ||||
| 	renderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "markdown_file.md") | ||||
| 	unrenderableFileURL := util.URLJoin(tree, "file.zip") | ||||
| 	unrenderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "file.zip") | ||||
| 	unrenderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "file.zip") | ||||
| 	favicon := "http://google.com/favicon.ico" | ||||
|  | ||||
| 	test( | ||||
|   | ||||
| @@ -672,9 +672,9 @@ space</p> | ||||
| 			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/raw/file.bin" rel="nofollow">local 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/file.bin" rel="nofollow">local 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="/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/path/file" alt="local image"/></a><br/> | ||||
| @@ -730,9 +730,9 @@ space</p> | ||||
| 			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/raw/file.bin" rel="nofollow">local 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/file.bin" rel="nofollow">local 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://gitea.io/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/path/file" alt="local image"/></a><br/> | ||||
| @@ -788,9 +788,9 @@ space</p> | ||||
| 			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/raw/file.bin" rel="nofollow">local 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/file.bin" rel="nofollow">local 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="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/> | ||||
| @@ -848,9 +848,9 @@ space</p> | ||||
| 			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/raw/file.bin" rel="nofollow">local 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/file.bin" rel="nofollow">local 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="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/> | ||||
| @@ -908,9 +908,9 @@ space</p> | ||||
| 			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/raw/file.bin" rel="nofollow">local 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/file.bin" rel="nofollow">local 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="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/> | ||||
| @@ -970,9 +970,9 @@ space</p> | ||||
| 			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/raw/file.bin" rel="nofollow">local 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/file.bin" rel="nofollow">local 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="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/> | ||||
|   | ||||
| @@ -138,18 +138,41 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { | ||||
| 	return content | ||||
| } | ||||
|  | ||||
| // wikiContentsByName returns the contents of a wiki page, along with a boolean | ||||
| // indicating whether the page exists. Writes to ctx if an error occurs. | ||||
| func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) { | ||||
| // wikiEntryByName returns the entry of a wiki page, along with a boolean | ||||
| // indicating whether the entry exists. Writes to ctx if an error occurs. | ||||
| // The last return value indicates whether the file should be returned as a raw file | ||||
| func wikiEntryByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) (*git.TreeEntry, string, bool, bool) { | ||||
| 	isRaw := false | ||||
| 	gitFilename := wiki_service.WebPathToGitPath(wikiName) | ||||
| 	entry, err := findEntryForFile(commit, gitFilename) | ||||
| 	if err != nil && !git.IsErrNotExist(err) { | ||||
| 		ctx.ServerError("findEntryForFile", err) | ||||
| 		return nil, nil, "", false | ||||
| 	} else if entry == nil { | ||||
| 		return nil, "", false, false | ||||
| 	} | ||||
| 	if entry == nil { | ||||
| 		// check if the file without ".md" suffix exists | ||||
| 		gitFilename := strings.TrimSuffix(gitFilename, ".md") | ||||
| 		entry, err = findEntryForFile(commit, gitFilename) | ||||
| 		if err != nil && !git.IsErrNotExist(err) { | ||||
| 			ctx.ServerError("findEntryForFile", err) | ||||
| 			return nil, "", false, false | ||||
| 		} | ||||
| 		isRaw = true | ||||
| 	} | ||||
| 	if entry == nil { | ||||
| 		return nil, "", true, false | ||||
| 	} | ||||
| 	return entry, gitFilename, false, isRaw | ||||
| } | ||||
|  | ||||
| // wikiContentsByName returns the contents of a wiki page, along with a boolean | ||||
| // indicating whether the page exists. Writes to ctx if an error occurs. | ||||
| func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) { | ||||
| 	entry, gitFilename, noEntry, _ := wikiEntryByName(ctx, commit, wikiName) | ||||
| 	if entry == nil { | ||||
| 		return nil, nil, "", true | ||||
| 	} | ||||
| 	return wikiContentsByEntry(ctx, entry), entry, gitFilename, false | ||||
| 	return wikiContentsByEntry(ctx, entry), entry, gitFilename, noEntry | ||||
| } | ||||
|  | ||||
| func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { | ||||
| @@ -215,11 +238,14 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { | ||||
| 	isSideBar := pageName == "_Sidebar" | ||||
| 	isFooter := pageName == "_Footer" | ||||
|  | ||||
| 	// lookup filename in wiki - get filecontent, gitTree entry , real filename | ||||
| 	data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName) | ||||
| 	// lookup filename in wiki - get gitTree entry , real filename | ||||
| 	entry, pageFilename, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName) | ||||
| 	if noEntry { | ||||
| 		ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages") | ||||
| 	} | ||||
| 	if isRaw { | ||||
| 		ctx.Redirect(util.URLJoin(ctx.Repo.RepoLink, "wiki/raw", string(pageName))) | ||||
| 	} | ||||
| 	if entry == nil || ctx.Written() { | ||||
| 		if wikiRepo != nil { | ||||
| 			wikiRepo.Close() | ||||
| @@ -227,6 +253,15 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	// get filecontent | ||||
| 	data := wikiContentsByEntry(ctx, entry) | ||||
| 	if ctx.Written() { | ||||
| 		if wikiRepo != nil { | ||||
| 			wikiRepo.Close() | ||||
| 		} | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	var sidebarContent []byte | ||||
| 	if !isSideBar { | ||||
| 		sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, "_Sidebar") | ||||
| @@ -442,15 +477,24 @@ func renderEditPage(ctx *context.Context) { | ||||
| 	ctx.Data["Title"] = displayName | ||||
| 	ctx.Data["title"] = displayName | ||||
|  | ||||
| 	// lookup filename in wiki - get filecontent, gitTree entry , real filename | ||||
| 	data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName) | ||||
| 	// lookup filename in wiki -  gitTree entry , real filename | ||||
| 	entry, _, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName) | ||||
| 	if noEntry { | ||||
| 		ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages") | ||||
| 	} | ||||
| 	if isRaw { | ||||
| 		ctx.Error(http.StatusForbidden, "Editing of raw wiki files is not allowed") | ||||
| 	} | ||||
| 	if entry == nil || ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// get filecontent | ||||
| 	data := wikiContentsByEntry(ctx, entry) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ctx.Data["content"] = string(data) | ||||
| 	ctx.Data["sidebarPresent"] = false | ||||
| 	ctx.Data["sidebarContent"] = "" | ||||
|   | ||||
| @@ -87,6 +87,13 @@ func TestWiki(t *testing.T) { | ||||
| 	assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) | ||||
| 	assert.EqualValues(t, "Home", ctx.Data["Title"]) | ||||
| 	assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"]) | ||||
|  | ||||
| 	ctx, _ = contexttest.MockContext(t, "user2/repo1/jpeg.jpg") | ||||
| 	ctx.SetPathParam("*", "jpeg.jpg") | ||||
| 	contexttest.LoadRepo(t, ctx, 1) | ||||
| 	Wiki(ctx) | ||||
| 	assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) | ||||
| 	assert.Equal(t, "/user2/repo1/wiki/raw/jpeg.jpg", ctx.Resp.Header().Get("Location")) | ||||
| } | ||||
|  | ||||
| func TestWikiPages(t *testing.T) { | ||||
| @@ -160,6 +167,13 @@ func TestEditWiki(t *testing.T) { | ||||
| 	assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) | ||||
| 	assert.EqualValues(t, "Home", ctx.Data["Title"]) | ||||
| 	assert.Equal(t, wikiContent(t, ctx.Repo.Repository, "Home"), ctx.Data["content"]) | ||||
|  | ||||
| 	ctx, _ = contexttest.MockContext(t, "user2/repo1/wiki/jpeg.jpg?action=_edit") | ||||
| 	ctx.SetPathParam("*", "jpeg.jpg") | ||||
| 	contexttest.LoadUser(t, ctx, 2) | ||||
| 	contexttest.LoadRepo(t, ctx, 1) | ||||
| 	EditWiki(ctx) | ||||
| 	assert.EqualValues(t, http.StatusForbidden, ctx.Resp.Status()) | ||||
| } | ||||
|  | ||||
| func TestEditWikiPost(t *testing.T) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user