diff options
author | tjpcc <tjp@ctrl-c.club> | 2023-01-28 14:52:35 -0700 |
---|---|---|
committer | tjpcc <tjp@ctrl-c.club> | 2023-01-28 15:01:41 -0700 |
commit | 66a1b1f39a1e1d5499b548b36d18c8daa872d7da (patch) | |
tree | 96471dbd5486ede1a908790ac23e0c55b226dfad /contrib/fs/gemini.go | |
parent | a27b879accb191b6a6c6e76a6251ed751967f73a (diff) |
gopher support.
Some of the contrib packages were originally built gemini-specific and
had to be refactored into generic core functionality and thin
protocol-specific wrappers for each of gemini and gopher.
Diffstat (limited to 'contrib/fs/gemini.go')
-rw-r--r-- | contrib/fs/gemini.go | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/contrib/fs/gemini.go b/contrib/fs/gemini.go new file mode 100644 index 0000000..b41cb75 --- /dev/null +++ b/contrib/fs/gemini.go @@ -0,0 +1,130 @@ +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.Redirect(url.String()) + } + + return path, dir, nil +} |