Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 18:15 UTC
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>
Aziz Rmadi committed
Feb 5, 2024 at 22:31 UTC
feb07a7b59a3ed6f518e5ce62f29d4816a51c5e7
5 files changed
+50
-18
modules/caddyhttp/fileserver/browse.go
+2
@@ -50,6 +50,8 @@ var BrowseTemplate string
50
type Browse struct {
51
// Filename of the template to use instead of the embedded browse template.
52
TemplateFile string `json:"template_file,omitempty"`
53
+ // Determines whether or not targets of symlinks should be revealed.
54
+ RevealSymlinks bool `json:"reveal_symlinks,omitempty"`
55
}
56
57
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
modules/caddyhttp/fileserver/browse.html
+8
@@ -962,7 +962,15 @@ footer {
962
<td>
963
<a href="{{html .URL}}">
964
{{template "icon" .}}
965
+ {{- if not .SymlinkPath}}
966
<span class="name">{{html .Name}}</span>
967
+ {{- else}}
968
+ <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">
969
+ <path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" />
970
+ <path d="M15 16l4 -4" />
971
+ <path d="M15 8l4 4" />
972
+ </svg> {{html .SymlinkPath}}</span>
973
+ {{- end}}
974
</a>
975
</td>
976
{{- if .IsDir}}
modules/caddyhttp/fileserver/browsetplcontext.go
+27
-15
@@ -20,6 +20,7 @@ import (
20
"net/url"
21
"os"
22
"path"
23
+ "path/filepath"
24
"sort"
25
"strconv"
26
"strings"
@@ -74,12 +75,21 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
75
76
size := info.Size()
77
fileIsSymlink := isSymlink(info)
78
+ symlinkPath := ""
79
if fileIsSymlink {
80
path := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, info.Name()))
81
fileInfo, err := fs.Stat(fileSystem, path)
82
if err == nil {
83
size = fileInfo.Size()
84
}
85
+
86
+ if fsrv.Browse.RevealSymlinks {
87
+ symLinkTarget, err := filepath.EvalSymlinks(path)
88
+ if err == nil {
89
+ symlinkPath = symLinkTarget
90
+ }
91
+ }
92
+
93
// An error most likely means the symlink target doesn't exist,
94
// which isn't entirely unusual and shouldn't fail the listing.
95
// 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,
103
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
104
105
tplCtx.Items = append(tplCtx.Items, fileInfo{
96
- IsDir: isDir,
97
- IsSymlink: fileIsSymlink,
98
- Name: name,
99
- Size: size,
100
- URL: u.String(),
101
- ModTime: info.ModTime().UTC(),
102
- Mode: info.Mode(),
103
- Tpl: tplCtx, // a reference up to the template context is useful
106
+ IsDir: isDir,
107
+ IsSymlink: fileIsSymlink,
108
+ Name: name,
109
+ Size: size,
110
+ URL: u.String(),
111
+ ModTime: info.ModTime().UTC(),
112
+ Mode: info.Mode(),
113
+ Tpl: tplCtx, // a reference up to the template context is useful
114
+ SymlinkPath: symlinkPath,
115
})
116
}
117
@@ -230,13 +241,14 @@ type crumb struct {
241
// fileInfo contains serializable information
242
// about a file or directory.
243
type fileInfo struct {
233
- Name string `json:"name"`
234
- Size int64 `json:"size"`
235
- URL string `json:"url"`
236
- ModTime time.Time `json:"mod_time"`
237
- Mode os.FileMode `json:"mode"`
238
- IsDir bool `json:"is_dir"`
239
- IsSymlink bool `json:"is_symlink"`
244
+ Name string `json:"name"`
245
+ Size int64 `json:"size"`
246
+ URL string `json:"url"`
247
+ ModTime time.Time `json:"mod_time"`
248
+ Mode os.FileMode `json:"mode"`
249
+ IsDir bool `json:"is_dir"`
250
+ IsSymlink bool `json:"is_symlink"`
251
+ SymlinkPath string `json:"symlink_path,omitempty"`
252
253
// a pointer to the template context is useful inside nested templates
254
Tpl *browseTemplateContext `json:"-"`
modules/caddyhttp/fileserver/caddyfile.go
+9
@@ -112,6 +112,15 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
112
}
113
fsrv.Browse = new(Browse)
114
d.Args(&fsrv.Browse.TemplateFile)
115
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
116
+ if d.Val() != "reveal_symlinks" {
117
+ return d.Errf("unknown subdirective '%s'", d.Val())
118
+ }
119
+ if fsrv.Browse.RevealSymlinks {
120
+ return d.Err("Symlinks path reveal is already enabled")
121
+ }
122
+ fsrv.Browse.RevealSymlinks = true
123
+ }
124
125
case "precompressed":
126
var order []string
modules/caddyhttp/fileserver/command.go
+4
-3
@@ -39,7 +39,7 @@ import (
39
func init() {
40
caddycmd.RegisterCommand(caddycmd.Command{
41
Name: "file-server",
42
- Usage: "[--domain <example.com>] [--root <path>] [--listen <addr>] [--browse] [--access-log] [--precompressed]",
42
+ Usage: "[--domain <example.com>] [--root <path>] [--listen <addr>] [--browse] [--reveal-symlinks] [--access-log] [--precompressed]",
43
Short: "Spins up a production-ready file server",
44
Long: `
45
A simple but production-ready file server. Useful for quick deployments,
@@ -62,6 +62,7 @@ respond with a file listing.`,
62
cmd.Flags().StringP("root", "r", "", "The path to the root of the site")
63
cmd.Flags().StringP("listen", "l", "", "The address to which to bind the listener")
64
cmd.Flags().BoolP("browse", "b", false, "Enable directory browsing")
65
+ cmd.Flags().BoolP("reveal-symlinks", "", false, "Show symlink paths when browse is enabled.")
66
cmd.Flags().BoolP("templates", "t", false, "Enable template rendering")
67
cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
68
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
@@ -91,12 +92,12 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
92
templates := fs.Bool("templates")
93
accessLog := fs.Bool("access-log")
94
debug := fs.Bool("debug")
95
+ revealSymlinks := fs.Bool("reveal-symlinks")
96
compress := !fs.Bool("no-compress")
97
precompressed, err := fs.GetStringSlice("precompressed")
98
if err != nil {
99
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid precompressed flag: %v", err)
100
}
99
-
101
var handlers []json.RawMessage
102
103
if compress {
@@ -150,7 +151,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
151
}
152
153
if browse {
153
- handler.Browse = new(Browse)
154
+ handler.Browse = &Browse{RevealSymlinks: revealSymlinks}
155
}
156
157
handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil))