131 lines
3.8 KiB
Go
131 lines
3.8 KiB
Go
package fs
|
|
|
|
import (
|
|
"context"
|
|
"io/fs"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"tildegit.org/tjp/gus"
|
|
"tildegit.org/tjp/gus/gemini"
|
|
)
|
|
|
|
// GeminiFileHandler builds a handler which serves up files from a file system.
|
|
//
|
|
// It only serves responses for paths which do not correspond to directories on disk.
|
|
func GeminiFileHandler(fileSystem fs.FS) gus.Handler {
|
|
return func(ctx context.Context, request *gus.Request) *gus.Response {
|
|
filepath, file, err := ResolveFile(request, fileSystem)
|
|
if err != nil {
|
|
return gemini.Failure(err)
|
|
}
|
|
|
|
if file == nil {
|
|
return nil
|
|
}
|
|
|
|
return gemini.Success(mediaType(filepath), file)
|
|
}
|
|
}
|
|
|
|
// GeminiDirectoryDefault 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 gemini 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 a URL with the trailing slash appended. This is necessary for relative
|
|
// links inot 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 GeminiDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler {
|
|
return func(ctx context.Context, request *gus.Request) *gus.Response {
|
|
dirpath, dir, response := handleDirGemini(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 gemini.Failure(err)
|
|
}
|
|
if file == nil {
|
|
return nil
|
|
}
|
|
|
|
return gemini.Success(mediaType(filepath), file)
|
|
}
|
|
}
|
|
|
|
// GeminiDirectoryListing produces a listing of the contents of any requested directories.
|
|
//
|
|
// It returns "51 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 inot 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.
|
|
//
|
|
// The template may be nil, in which case DefaultGeminiDirectoryList is used instead. The
|
|
// template is then processed with RenderDirectoryListing.
|
|
func GeminiDirectoryListing(fileSystem fs.FS, template *template.Template) gus.Handler {
|
|
return func(ctx context.Context, request *gus.Request) *gus.Response {
|
|
dirpath, dir, response := handleDirGemini(request, fileSystem)
|
|
if response != nil {
|
|
return response
|
|
}
|
|
if dir == nil {
|
|
return nil
|
|
}
|
|
defer func() { _ = dir.Close() }()
|
|
|
|
if template == nil {
|
|
template = DefaultGeminiDirectoryList
|
|
}
|
|
body, err := RenderDirectoryListing(dirpath, dir, template, request.Server)
|
|
if err != nil {
|
|
return gemini.Failure(err)
|
|
}
|
|
|
|
return gemini.Success("text/gemini", body)
|
|
}
|
|
}
|
|
|
|
// DefaultGeminiDirectoryList is a template which renders a reasonable gemtext dir list.
|
|
var DefaultGeminiDirectoryList = template.Must(template.New("gemini_dirlist").Parse(`
|
|
# {{ .DirName }}
|
|
{{ range .Entries }}
|
|
=> {{ .Name }}{{ if .IsDir }}/{{ end -}}
|
|
{{ end }}
|
|
=> ../
|
|
`[1:]))
|
|
|
|
func handleDirGemini(request *gus.Request, fileSystem fs.FS) (string, fs.ReadDirFile, *gus.Response) {
|
|
path, dir, err := ResolveDirectory(request, fileSystem)
|
|
if err != nil {
|
|
return "", nil, gemini.Failure(err)
|
|
}
|
|
|
|
if dir == nil {
|
|
return "", nil, nil
|
|
}
|
|
|
|
if !strings.HasSuffix(request.Path, "/") {
|
|
_ = dir.Close()
|
|
url := *request.URL
|
|
url.Path += "/"
|
|
return "", nil, gemini.PermanentRedirect(url.String())
|
|
}
|
|
|
|
return path, dir, nil
|
|
}
|