mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -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
					
				@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user