@marcel91200 / caddy Mirror · read-only / commits / feb07a7b
Fork 0

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))