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/gopher.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/gopher.go')
-rw-r--r-- | contrib/fs/gopher.go | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/contrib/fs/gopher.go b/contrib/fs/gopher.go new file mode 100644 index 0000000..7b0d8bd --- /dev/null +++ b/contrib/fs/gopher.go @@ -0,0 +1,168 @@ +package fs + +import ( + "context" + "io/fs" + "mime" + "path" + "strings" + "text/template" + + "tildegit.org/tjp/gus" + "tildegit.org/tjp/gus/gopher" +) + +// GopherFileHandler builds a handler which serves up files from a file system. +// +// It only serves responses for paths which correspond to files, not directories. +func GopherFileHandler(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 gopher.Error(err).Response() + } + + if file == nil { + return nil + } + + return gopher.File(GuessGopherItemType(filepath), file) + } +} + +// GopherDirectoryDefault serves up default files for directory path requests. +// +// If any of the supported filenames are found in the requested directory, the +// contents of that file is returned as the gopher response. +// +// It returns nil for any paths which don't correspond to a directory. +// +// It requires that files from the provided fs.FS implement fs.ReadDirFile. If +// they don't, it will produce nil responses for all directory paths. +func GopherDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler { + return func(ctx context.Context, request *gus.Request) *gus.Response { + dirpath, dir, err := ResolveDirectory(request, fileSystem) + if err != nil { + return gopher.Error(err).Response() + } + if dir == nil { + return nil + } + defer func() { _ = dir.Close() }() + + _, file, err := ResolveDirectoryDefault(fileSystem, dirpath, dir, filenames) + if err != nil { + return gopher.Error(err).Response() + } + if file == nil { + return nil + } + + return gopher.File(gopher.MenuType, file) + } +} + +// GopherDirectoryListing produces a listing of the contents of any requested directories. +// +// It returns nil for any paths which don't correspond to a filesystem directory. +// +// 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. +// +// A template may be nil, in which case DefaultGopherDirectoryList is used instead. The +// template is then processed with RenderDirectoryListing. +func GopherDirectoryListing(fileSystem fs.FS, tpl *template.Template) gus.Handler { + return func(ctx context.Context, request *gus.Request) *gus.Response { + dirpath, dir, err := ResolveDirectory(request, fileSystem) + if err != nil { + return gopher.Error(err).Response() + } + if dir == nil { + return nil + } + defer func() { _ = dir.Close() }() + + if tpl == nil { + tpl = DefaultGopherDirectoryList + } + body, err := RenderDirectoryListing(dirpath, dir, tpl, request.Server) + if err != nil { + return gopher.Error(err).Response() + } + + return gopher.File(gopher.MenuType, body) + } +} + +// GopherTemplateFunctions is a map for templates providing useful functions for gophermaps. +// +// - GuessItemType: return a gopher item type for a file based on its path/name. +var GopherTemplateFunctions = template.FuncMap{ + "GuessItemType": func(filepath string) string { + return string([]byte{byte(GuessGopherItemType(filepath))}) + }, +} + +// DefaultGopherDirectoryList is a template which renders a directory listing as gophermap. +var DefaultGopherDirectoryList = template.Must( + template.New("gopher_dirlist").Funcs(GopherTemplateFunctions).Parse( + strings.ReplaceAll( + ` +{{ $root := .FullPath -}} +{{ if eq .FullPath "." }}{{ $root = "" }}{{ end -}} +{{ $hostname := .Hostname -}} +{{ $port := .Port -}} +i{{ .DirName }} {{ $hostname }} {{ $port }} +i {{ $hostname }} {{ $port }} +{{ range .Entries -}} +{{ if .IsDir -}} +1{{ .Name }} {{ $root }}/{{ .Name }} {{ $hostname }} {{ $port }} +{{- else -}} +{{ GuessItemType .Name }}{{ .Name }} {{ $root }}/{{ .Name }} {{ $hostname }} {{ $port }} +{{- end }} +{{ end -}} +. +`[1:], + "\n", + "\r\n", + ), + ), +) + +// GuessGopherItemType attempts to find the best gopher item type for a file based on its name. +func GuessGopherItemType(filepath string) gus.Status { + ext := path.Ext(filepath) + switch ext { + case "txt", "gmi": + return gopher.TextFileType + case "gif", "png", "jpg", "jpeg": + return gopher.ImageFileType + case "mp4", "mov": + return gopher.MovieFileType + case "mp3", "aiff", "aif", "aac", "ogg", "flac", "alac", "wma": + return gopher.SoundFileType + case "bmp": + return gopher.BitmapType + case "doc", "docx", "odt": + return gopher.DocumentType + case "html", "htm": + return gopher.HTMLType + case "rtf": + return gopher.RtfDocumentType + case "wav": + return gopher.WavSoundFileType + case "pdf": + return gopher.PdfDocumentType + case "xml": + return gopher.XmlDocumentType + case "": + return gopher.BinaryFileType + } + + mtype := mime.TypeByExtension(ext) + if strings.HasPrefix(mtype, "text/") { + return gopher.TextFileType + } + + return gopher.BinaryFileType +} |