From 16a6372cc9312227c241e93deee004ec703bc162 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 5 Jul 2020 23:27:32 +0300 Subject: [PATCH] add a DirListRich helper to make the file listing a bit more appealing than its default Former-commit-id: 1d8338cddac0856be1c9f1e7b6d8d400bee71bef --- _examples/file-server/file-server/main.go | 3 +- core/router/fs.go | 195 ++++++++++++++++++++-- iris.go | 4 + 3 files changed, 186 insertions(+), 16 deletions(-) diff --git a/_examples/file-server/file-server/main.go b/_examples/file-server/file-server/main.go index 3e0bdb68..c67881f2 100644 --- a/_examples/file-server/file-server/main.go +++ b/_examples/file-server/file-server/main.go @@ -31,6 +31,7 @@ func main() { app.HandleDir("/files", "./uploads", iris.DirOptions{ Gzip: true, ShowList: true, + DirList: iris.DirListRich, }) app.Listen(":8080") @@ -49,7 +50,7 @@ func uploadView(ctx iris.Context) { ctx.View("upload.html", token) } -const maxSize = 10 * iris.MB +const maxSize = 1 * iris.GB func upload(ctx iris.Context) { ctx.SetMaxRequestBodySize(maxSize) diff --git a/core/router/fs.go b/core/router/fs.go index 1fd0bd95..a8cfecf9 100644 --- a/core/router/fs.go +++ b/core/router/fs.go @@ -3,6 +3,8 @@ package router import ( "bytes" "fmt" + "html" + "html/template" "io" "io/ioutil" "net/http" @@ -19,6 +21,9 @@ import ( const indexName = "/index.html" +// DirListFunc is the function signature for customizing directory and file listing. +type DirListFunc func(ctx context.Context, dirName string, dir http.File) error + // DirOptions contains the optional settings that // `FileServer` and `Party#HandleDir` can use to serve files and assets. type DirOptions struct { @@ -33,7 +38,7 @@ type DirOptions struct { // List the files inside the current requested directory if `IndexName` not found. ShowList bool // If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir). - DirList func(ctx context.Context, dirName string, dir http.File) error + DirList DirListFunc // When embedded. Asset func(name string) ([]byte, error) // we need this to make it compatible os.File. @@ -283,16 +288,6 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { ctx.StatusCode(statusCode) } - htmlReplacer := strings.NewReplacer( - "&", "&", - "<", "<", - ">", ">", - // """ is shorter than """. - `"`, """, - // "'" is shorter than "'" and apos was not in HTML until HTML5. - "'", "'", - ) - dirList := options.DirList if dirList == nil { dirList = func(ctx context.Context, dirName string, dir http.File) error { @@ -301,9 +296,6 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { return err } - // dst, _ := dir.Stat() - // dirName := dst.Name() - sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) ctx.ContentType(context.ContentHTMLHeaderValue) @@ -332,7 +324,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { // name may contain '?' or '#', which must be escaped to remain // part of the URL path, and not indicate the start of a query // string or fragment. - _, err = ctx.Writef("%s\n", url.String(), htmlReplacer.Replace(name)) + _, err = ctx.Writef("%s\n", url.String(), html.EscapeString(name)) if err != nil { return err } @@ -403,6 +395,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { ctx.SetLastModified(info.ModTime()) err = dirList(ctx, info.Name(), f) if err != nil { + println(err.Error()) plainStatusCode(ctx, http.StatusInternalServerError) return } @@ -572,3 +565,175 @@ func DirectoryExists(dir string) bool { } return true } + +// DirListRich is a `DirListFunc` which can be passed to `DirOptions.DirList` field +// to override the default file listing appearance. +// See `DirListRichTemplate` to modify the template, if necessary. +func DirListRich(ctx context.Context, dirName string, dir http.File) error { + dirs, err := dir.Readdir(-1) + if err != nil { + return err + } + + sortBy := ctx.URLParam("sort") + switch sortBy { + case "name": + sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) + case "size": + sort.Slice(dirs, func(i, j int) bool { return dirs[i].Size() < dirs[j].Size() }) + default: + sort.Slice(dirs, func(i, j int) bool { return dirs[i].ModTime().After(dirs[j].ModTime()) }) + } + + pageData := listPageData{ + Title: fmt.Sprintf("List of %d files", len(dirs)), + Files: make([]fileInfoData, 0, len(dirs)), + } + + for _, d := range dirs { + name := d.Name() + if d.IsDir() { + name += "/" + } + + upath := "" + if ctx.Path() == "/" { + upath = ctx.GetCurrentRoute().StaticPath() + "/" + name + } else { + upath = "./" + dirName + "/" + name + } + + url := url.URL{Path: upath} + + pageData.Files = append(pageData.Files, fileInfoData{ + Info: d, + ModTime: d.ModTime().UTC().Format(http.TimeFormat), + Path: url.String(), + Name: html.EscapeString(name), + }) + } + + return DirListRichTemplate.Execute(ctx, pageData) +} + +type ( + listPageData struct { + Title string // the document's title. + Files []fileInfoData + } + + fileInfoData struct { + Info os.FileInfo + ModTime string // format-ed time. + Path string // the request path. + Name string // the html-escaped name. + } +) + +// DirListRichTemplate is the html template the `DirListRich` function is using to render +// the directories and files. +var DirListRichTemplate = template.Must(template.New(""). + Funcs(template.FuncMap{ + "formatBytes": func(b int64) string { + const unit = 1000 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", + float64(b)/float64(div), "kMGTPE"[exp]) + }, + }).Parse(` + + + + + + + {{.Title}} + + + + + + + + + + + + + {{ range $idx, $file := .Files }} + + + + + + {{ end }} + +
#NameSize
{{ $idx }}{{ $file.Name }}{{ formatBytes $file.Info.Size }}
+ +`)) diff --git a/iris.go b/iris.go index e8aa12d9..0aa3f0f5 100644 --- a/iris.go +++ b/iris.go @@ -439,6 +439,10 @@ var ( // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server // A shortcut for the `router.FileServer`. FileServer = router.FileServer + // DirListRich can be passed to `DirOptions.DirList` field + // to override the default file listing appearance. + // Read more at: `core/router.DirListRich`. + DirListRich = router.DirListRich // StripPrefix returns a handler that serves HTTP requests // by removing the given prefix from the request URL's Path // and invoking the handler h. StripPrefix handles a