diff options
author | tjpcc <tjp@ctrl-c.club> | 2023-04-29 20:56:15 -0600 |
---|---|---|
committer | tjpcc <tjp@ctrl-c.club> | 2023-04-29 20:56:15 -0600 |
commit | 7a021631cd9e02abe62610f66567f81062cecfbe (patch) | |
tree | 78da4793a4f16ad8ecc33c58157aea4f277dbfa1 | |
parent | 3ff04cf88571f8ed1aca78da4efe4929ad583ca6 (diff) |
spartan FS server
-rw-r--r-- | contrib/fs/gemini.go | 4 | ||||
-rw-r--r-- | contrib/fs/spartan.go | 124 |
2 files changed, 126 insertions, 2 deletions
diff --git a/contrib/fs/gemini.go b/contrib/fs/gemini.go index 15677f1..21ca32d 100644 --- a/contrib/fs/gemini.go +++ b/contrib/fs/gemini.go @@ -37,7 +37,7 @@ func GeminiFileHandler(fileSystem fs.FS) gus.Handler { // // When it encounters a directory path which doesn't end in a trailing slash (/) it // redirects to a URL with the trailing slash appended. This is necessary for relative -// links inot the directory's contents to function properly. +// links not the directory's contents to function properly. // // It requires that files from the provided fs.FS implement fs.ReadDirFile. If they // don't, it will produce nil responses for any directory paths. @@ -70,7 +70,7 @@ func GeminiDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler { // // When it encounters a directory path which doesn't end in a trailing slash (/) it // redirects to a URL with the trailing slash appended. This is necessary for relative -// links inot the directory's contents to function properly. +// links not the directory's contents to function properly. // // It requires that files from the provided fs.FS implement fs.ReadDirFile. If they // don't, it will produce "51 Not Found" responses for any directory paths. diff --git a/contrib/fs/spartan.go b/contrib/fs/spartan.go new file mode 100644 index 0000000..550f549 --- /dev/null +++ b/contrib/fs/spartan.go @@ -0,0 +1,124 @@ +package fs + +import ( + "context" + "io/fs" + "strings" + "text/template" + + "tildegit.org/tjp/gus" + "tildegit.org/tjp/gus/spartan" +) + +// SpartanFileHandler builds a handler which serves up files from a filesystem. +// +// It only serves responses for paths which do not correspond to directories on disk. +func SpartanFileHandler(fileSystem fs.FS) gus.Handler { + return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response { + filepath, file, err := ResolveFile(request, fileSystem) + if err != nil { + return spartan.ClientError(err) + } + + if file == nil { + return nil + } + + return spartan.Success(mediaType(filepath), file) + }) +} + +// SpartanDirectoryDefault serves up default files for directory path requests. +// +// If any of the supported filenames are found, the contents of the file is returned +// as the spartan response. +// +// It returns nil for any paths which don't correspond to a directory. +// +// When it encounters a directory path which doesn't end in a trailing slash (/) it +// redirects to the same URL with the slash appended. This is necessary for relative +// links not in the directory's contents to function properly. +// +// It requires that files from the provided fs.FS implement fs.ReadDirFile. If they +// don't, it will produce nil responses for any directory paths. +func SpartanDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler { + return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response { + dirpath, dir, response := handleDirSpartan(request, fileSystem) + if response != nil { + return response + } + if dir == nil { + return nil + } + defer func() { _ = dir.Close() }() + + filepath, file, err := ResolveDirectoryDefault(fileSystem, dirpath, dir, filenames) + if err != nil { + return spartan.ServerError(err) + } + if file == nil { + return nil + } + + return spartan.Success(mediaType(filepath), file) + }) +} + +// SpartanDirectoryListing produces a listing of the contents of any requested directories. +// +// It returns "4 Resource not found" for any paths which don't correspond to a filesystem directory. +// +// When it encounters a directory path which doesn't end in a trailing slash (/) it redirects to a +// URL with the trailing slash appended. This is necessary for relative links not in the directory's +// contents to function properly. +// +// It requires that files provided by the fs.FS implement fs.ReadDirFile. If they don't, it will +// produce "4 Resource not found" responses for any directory paths. +// +// The tmeplate may be nil, in which cause DefaultSpartanDirectoryList is used instead. The +// template is then processed with RenderDirectoryListing. +func SpartanDirectoryListing(filesystem fs.FS, template *template.Template) gus.Handler { + return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response { + dirpath, dir, response := handleDirSpartan(request, filesystem) + if response != nil { + return response + } + if dir == nil { + return nil + } + defer func() { _ = dir.Close() }() + + if template == nil { + template = DefaultSpartanDirectoryList + } + body, err := RenderDirectoryListing(dirpath, dir, template, request.Server) + if err != nil { + return spartan.ServerError(err) + } + + return spartan.Success("text/gemini", body) + }) +} + +// DefaultSpartanDirectoryList is a template which renders a reasonable gemtext dir listing. +var DefaultSpartanDirectoryList = DefaultGeminiDirectoryList + +func handleDirSpartan(request *gus.Request, filesystem fs.FS) (string, fs.ReadDirFile, *gus.Response) { + path, dir, err := ResolveDirectory(request, filesystem) + if err != nil { + return "", nil, spartan.ServerError(err) + } + + if dir == nil { + return "", nil, nil + } + + if !strings.HasSuffix(request.Path, "/") { + _ = dir.Close() + url := *request.URL + url.Path += "/" + return "", nil, spartan.Redirect(url.String()) + } + + return path, dir, nil +} |