diff --git a/modules/base/tool.go b/modules/base/tool.go index 168a2220b2..231507546d 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -174,7 +174,7 @@ func Int64sToStrings(ints []int64) []string { func EntryIcon(entry *git.TreeEntry) string { switch { case entry.IsLink(): - te, err := entry.FollowLink() + te, _, err := entry.FollowLink() if err != nil { log.Debug(err.Error()) return "file-symlink-file" diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 9513121487..2c47c8858c 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -23,15 +23,15 @@ func (te *TreeEntry) Type() string { } // FollowLink returns the entry pointed to by a symlink -func (te *TreeEntry) FollowLink() (*TreeEntry, error) { +func (te *TreeEntry) FollowLink() (*TreeEntry, string, error) { if !te.IsLink() { - return nil, ErrBadLink{te.Name(), "not a symlink"} + return nil, "", ErrBadLink{te.Name(), "not a symlink"} } // read the link r, err := te.Blob().DataAsync() if err != nil { - return nil, err + return nil, "", err } closed := false defer func() { @@ -42,7 +42,7 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) { buf := make([]byte, te.Size()) _, err = io.ReadFull(r, buf) if err != nil { - return nil, err + return nil, "", err } _ = r.Close() closed = true @@ -56,33 +56,35 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) { } if t == nil { - return nil, ErrBadLink{te.Name(), "points outside of repo"} + return nil, "", ErrBadLink{te.Name(), "points outside of repo"} } target, err := t.GetTreeEntryByPath(lnk) if err != nil { if IsErrNotExist(err) { - return nil, ErrBadLink{te.Name(), "broken link"} + return nil, "", ErrBadLink{te.Name(), "broken link"} } - return nil, err + return nil, "", err } - return target, nil + return target, lnk, nil } // FollowLinks returns the entry ultimately pointed to by a symlink -func (te *TreeEntry) FollowLinks() (*TreeEntry, error) { +func (te *TreeEntry) FollowLinks() (*TreeEntry, string, error) { if !te.IsLink() { - return nil, ErrBadLink{te.Name(), "not a symlink"} + return nil, "", ErrBadLink{te.Name(), "not a symlink"} } entry := te + entryLink := "" for i := 0; i < 999; i++ { if entry.IsLink() { - next, err := entry.FollowLink() + next, link, err := entry.FollowLink() + entryLink = link if err != nil { - return nil, err + return nil, "", err } if next.ID == entry.ID { - return nil, ErrBadLink{ + return nil, "", ErrBadLink{ entry.Name(), "recursive link", } @@ -93,12 +95,12 @@ func (te *TreeEntry) FollowLinks() (*TreeEntry, error) { } } if entry.IsLink() { - return nil, ErrBadLink{ + return nil, "", ErrBadLink{ te.Name(), "too many levels of symbolic links", } } - return entry, nil + return entry, entryLink, nil } // returns the Tree pointed to by this TreeEntry, or nil if this is not a tree diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 0a6b1e0d82..424b063796 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1205,6 +1205,7 @@ tag = Tag released_this = released this file.title = %s at %s file_raw = Raw +file_follow = Follow Symlink file_history = History file_view_source = View Source file_view_rendered = View Rendered diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 86977062cb..32c09bb5f2 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -114,7 +114,7 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try log.Debug("Potential readme file: %s", entry.Name()) if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) { if entry.IsLink() { - target, err := entry.FollowLinks() + target, _, err := entry.FollowLinks() if err != nil && !git.IsErrBadLink(err) { return "", nil, err } else if target != nil && (target.IsExecutable() || target.IsRegular()) { @@ -267,7 +267,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) { target := readmeFile if readmeFile != nil && readmeFile.IsLink() { - target, _ = readmeFile.FollowLinks() + target, _, _ = readmeFile.FollowLinks() } if target == nil { // if findReadmeFile() failed and/or gave us a broken symlink (which it shouldn't) @@ -391,6 +391,15 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["FileName"] = blob.Name() ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + if entry.IsLink() { + _, link, err := entry.FollowLinks() + // Errors should be allowed, because this shouldn't + // block rendering invalid symlink files. + if err == nil { + ctx.Data["SymlinkURL"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(link) + } + } + commit, err := ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath) if err != nil { ctx.ServerError("GetCommitByPath", err) diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index b8eb2393fe..91b10f744a 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -43,6 +43,9 @@ {{end}} {{if not .ReadmeInList}}