mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-30 22:34:15 -05:00
fileserver: Browse can show symlink target if enabled (#5973)
* Added optional subdirective to browse allowing to reveal symlink paths. * Update modules/caddyhttp/fileserver/browsetplcontext.go --------- Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
This commit is contained in:
parent
a7479302fc
commit
feb07a7b59
5 changed files with 50 additions and 18 deletions
|
@ -50,6 +50,8 @@ var BrowseTemplate string
|
||||||
type Browse struct {
|
type Browse struct {
|
||||||
// Filename of the template to use instead of the embedded browse template.
|
// Filename of the template to use instead of the embedded browse template.
|
||||||
TemplateFile string `json:"template_file,omitempty"`
|
TemplateFile string `json:"template_file,omitempty"`
|
||||||
|
// Determines whether or not targets of symlinks should be revealed.
|
||||||
|
RevealSymlinks bool `json:"reveal_symlinks,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
|
|
|
@ -962,7 +962,15 @@ footer {
|
||||||
<td>
|
<td>
|
||||||
<a href="{{html .URL}}">
|
<a href="{{html .URL}}">
|
||||||
{{template "icon" .}}
|
{{template "icon" .}}
|
||||||
|
{{- if not .SymlinkPath}}
|
||||||
<span class="name">{{html .Name}}</span>
|
<span class="name">{{html .Name}}</span>
|
||||||
|
{{- else}}
|
||||||
|
<span class="name">{{html .Name}} <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-narrow-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" />
|
||||||
|
<path d="M15 16l4 -4" />
|
||||||
|
<path d="M15 8l4 4" />
|
||||||
|
</svg> {{html .SymlinkPath}}</span>
|
||||||
|
{{- end}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
{{- if .IsDir}}
|
{{- if .IsDir}}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -74,12 +75,21 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
||||||
|
|
||||||
size := info.Size()
|
size := info.Size()
|
||||||
fileIsSymlink := isSymlink(info)
|
fileIsSymlink := isSymlink(info)
|
||||||
|
symlinkPath := ""
|
||||||
if fileIsSymlink {
|
if fileIsSymlink {
|
||||||
path := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, info.Name()))
|
path := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, info.Name()))
|
||||||
fileInfo, err := fs.Stat(fileSystem, path)
|
fileInfo, err := fs.Stat(fileSystem, path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
size = fileInfo.Size()
|
size = fileInfo.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fsrv.Browse.RevealSymlinks {
|
||||||
|
symLinkTarget, err := filepath.EvalSymlinks(path)
|
||||||
|
if err == nil {
|
||||||
|
symlinkPath = symLinkTarget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// An error most likely means the symlink target doesn't exist,
|
// An error most likely means the symlink target doesn't exist,
|
||||||
// which isn't entirely unusual and shouldn't fail the listing.
|
// which isn't entirely unusual and shouldn't fail the listing.
|
||||||
// In this case, just use the size of the symlink itself, which
|
// In this case, just use the size of the symlink itself, which
|
||||||
|
@ -93,14 +103,15 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
||||||
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
|
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
|
||||||
|
|
||||||
tplCtx.Items = append(tplCtx.Items, fileInfo{
|
tplCtx.Items = append(tplCtx.Items, fileInfo{
|
||||||
IsDir: isDir,
|
IsDir: isDir,
|
||||||
IsSymlink: fileIsSymlink,
|
IsSymlink: fileIsSymlink,
|
||||||
Name: name,
|
Name: name,
|
||||||
Size: size,
|
Size: size,
|
||||||
URL: u.String(),
|
URL: u.String(),
|
||||||
ModTime: info.ModTime().UTC(),
|
ModTime: info.ModTime().UTC(),
|
||||||
Mode: info.Mode(),
|
Mode: info.Mode(),
|
||||||
Tpl: tplCtx, // a reference up to the template context is useful
|
Tpl: tplCtx, // a reference up to the template context is useful
|
||||||
|
SymlinkPath: symlinkPath,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,13 +241,14 @@ type crumb struct {
|
||||||
// fileInfo contains serializable information
|
// fileInfo contains serializable information
|
||||||
// about a file or directory.
|
// about a file or directory.
|
||||||
type fileInfo struct {
|
type fileInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
ModTime time.Time `json:"mod_time"`
|
ModTime time.Time `json:"mod_time"`
|
||||||
Mode os.FileMode `json:"mode"`
|
Mode os.FileMode `json:"mode"`
|
||||||
IsDir bool `json:"is_dir"`
|
IsDir bool `json:"is_dir"`
|
||||||
IsSymlink bool `json:"is_symlink"`
|
IsSymlink bool `json:"is_symlink"`
|
||||||
|
SymlinkPath string `json:"symlink_path,omitempty"`
|
||||||
|
|
||||||
// a pointer to the template context is useful inside nested templates
|
// a pointer to the template context is useful inside nested templates
|
||||||
Tpl *browseTemplateContext `json:"-"`
|
Tpl *browseTemplateContext `json:"-"`
|
||||||
|
|
|
@ -112,6 +112,15 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
}
|
}
|
||||||
fsrv.Browse = new(Browse)
|
fsrv.Browse = new(Browse)
|
||||||
d.Args(&fsrv.Browse.TemplateFile)
|
d.Args(&fsrv.Browse.TemplateFile)
|
||||||
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
|
if d.Val() != "reveal_symlinks" {
|
||||||
|
return d.Errf("unknown subdirective '%s'", d.Val())
|
||||||
|
}
|
||||||
|
if fsrv.Browse.RevealSymlinks {
|
||||||
|
return d.Err("Symlinks path reveal is already enabled")
|
||||||
|
}
|
||||||
|
fsrv.Browse.RevealSymlinks = true
|
||||||
|
}
|
||||||
|
|
||||||
case "precompressed":
|
case "precompressed":
|
||||||
var order []string
|
var order []string
|
||||||
|
|
|
@ -39,7 +39,7 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
caddycmd.RegisterCommand(caddycmd.Command{
|
caddycmd.RegisterCommand(caddycmd.Command{
|
||||||
Name: "file-server",
|
Name: "file-server",
|
||||||
Usage: "[--domain <example.com>] [--root <path>] [--listen <addr>] [--browse] [--access-log] [--precompressed]",
|
Usage: "[--domain <example.com>] [--root <path>] [--listen <addr>] [--browse] [--reveal-symlinks] [--access-log] [--precompressed]",
|
||||||
Short: "Spins up a production-ready file server",
|
Short: "Spins up a production-ready file server",
|
||||||
Long: `
|
Long: `
|
||||||
A simple but production-ready file server. Useful for quick deployments,
|
A simple but production-ready file server. Useful for quick deployments,
|
||||||
|
@ -62,6 +62,7 @@ respond with a file listing.`,
|
||||||
cmd.Flags().StringP("root", "r", "", "The path to the root of the site")
|
cmd.Flags().StringP("root", "r", "", "The path to the root of the site")
|
||||||
cmd.Flags().StringP("listen", "l", "", "The address to which to bind the listener")
|
cmd.Flags().StringP("listen", "l", "", "The address to which to bind the listener")
|
||||||
cmd.Flags().BoolP("browse", "b", false, "Enable directory browsing")
|
cmd.Flags().BoolP("browse", "b", false, "Enable directory browsing")
|
||||||
|
cmd.Flags().BoolP("reveal-symlinks", "", false, "Show symlink paths when browse is enabled.")
|
||||||
cmd.Flags().BoolP("templates", "t", false, "Enable template rendering")
|
cmd.Flags().BoolP("templates", "t", false, "Enable template rendering")
|
||||||
cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
|
cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
|
||||||
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
|
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
|
||||||
|
@ -91,12 +92,12 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
|
||||||
templates := fs.Bool("templates")
|
templates := fs.Bool("templates")
|
||||||
accessLog := fs.Bool("access-log")
|
accessLog := fs.Bool("access-log")
|
||||||
debug := fs.Bool("debug")
|
debug := fs.Bool("debug")
|
||||||
|
revealSymlinks := fs.Bool("reveal-symlinks")
|
||||||
compress := !fs.Bool("no-compress")
|
compress := !fs.Bool("no-compress")
|
||||||
precompressed, err := fs.GetStringSlice("precompressed")
|
precompressed, err := fs.GetStringSlice("precompressed")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid precompressed flag: %v", err)
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid precompressed flag: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var handlers []json.RawMessage
|
var handlers []json.RawMessage
|
||||||
|
|
||||||
if compress {
|
if compress {
|
||||||
|
@ -150,7 +151,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if browse {
|
if browse {
|
||||||
handler.Browse = new(Browse)
|
handler.Browse = &Browse{RevealSymlinks: revealSymlinks}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil))
|
handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil))
|
||||||
|
|
Loading…
Reference in a new issue