From 45132c5b24b411afbf90e8f897a1598d4eca790f Mon Sep 17 00:00:00 2001 From: kylosus <33132401+kylosus@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:12:40 +0300 Subject: [PATCH] caddyhttp: Add plaintext response to `file_server browse` (#6093) * Added plaintext support to file_server browser This commit is twofold: First it adds a new optional field, `return_type`, to `browser` for setting the default format of the returned index (html, json or plaintext). This is used when the `Accept` header is set to `/*`. Second, it adds a preliminary `text/plain` support to the `file_server` browser that returns a text representation of the file system, when an `Accept: text/plain` header is present, with the behavior discussed above. * Added more details and better formatting to plaintext browser * Replaced returnType conditions with a switch statement * Simplify --------- Co-authored-by: Matt Holt --- modules/caddyhttp/fileserver/browse.go | 36 +++++++++++++++++++++-- modules/caddyhttp/fileserver/caddyfile.go | 12 ++++---- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/modules/caddyhttp/fileserver/browse.go b/modules/caddyhttp/fileserver/browse.go index 86adc7e3..e0ea171f 100644 --- a/modules/caddyhttp/fileserver/browse.go +++ b/modules/caddyhttp/fileserver/browse.go @@ -28,6 +28,7 @@ import ( "path" "strings" "sync" + "text/tabwriter" "text/template" "go.uber.org/zap" @@ -111,13 +112,42 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ",")) - // write response as either JSON or HTML - if strings.Contains(acceptHeader, "application/json") { + switch { + case strings.Contains(acceptHeader, "application/json"): if err := json.NewEncoder(buf).Encode(listing.Items); err != nil { return caddyhttp.Error(http.StatusInternalServerError, err) } w.Header().Set("Content-Type", "application/json; charset=utf-8") - } else { + + case strings.Contains(acceptHeader, "text/plain"): + writer := tabwriter.NewWriter(buf, 0, 8, 1, '\t', tabwriter.AlignRight) + + // Header on top + if _, err := fmt.Fprintln(writer, "Name\tSize\tModified"); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + // Lines to separate the header + if _, err := fmt.Fprintln(writer, "----\t----\t--------"); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + // Actual files + for _, item := range listing.Items { + if _, err := fmt.Fprintf(writer, "%s\t%s\t%s\n", + item.Name, item.HumanSize(), item.HumanModTime("January 2, 2006 at 15:04:05"), + ); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + } + + if err := writer.Flush(); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + + default: var fs http.FileSystem if fsrv.Root != "" { fs = http.Dir(repl.ReplaceAll(fsrv.Root, ".")) diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go index 6ad9190f..d90e4f9a 100644 --- a/modules/caddyhttp/fileserver/caddyfile.go +++ b/modules/caddyhttp/fileserver/caddyfile.go @@ -113,13 +113,15 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { fsrv.Browse = new(Browse) d.Args(&fsrv.Browse.TemplateFile) for nesting := d.Nesting(); d.NextBlock(nesting); { - if d.Val() != "reveal_symlinks" { + switch d.Val() { + case "reveal_symlinks": + if fsrv.Browse.RevealSymlinks { + return d.Err("Symlinks path reveal is already enabled") + } + fsrv.Browse.RevealSymlinks = true + default: 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":