diff options
Diffstat (limited to 'contrib/fs/gopher.go')
-rw-r--r-- | contrib/fs/gopher.go | 183 |
1 files changed, 113 insertions, 70 deletions
diff --git a/contrib/fs/gopher.go b/contrib/fs/gopher.go index 0594730..4d86ba6 100644 --- a/contrib/fs/gopher.go +++ b/contrib/fs/gopher.go @@ -2,29 +2,56 @@ package fs import ( "context" - "io/fs" + "os" + "path/filepath" + "slices" "strings" - "text/template" sr "tildegit.org/tjp/sliderule" "tildegit.org/tjp/sliderule/gopher" + "tildegit.org/tjp/sliderule/gopher/gophermap" ) // 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) sr.Handler { +func GopherFileHandler(rootpath string, settings *gophermap.FileSystemSettings) sr.Handler { return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { - filepath, file, err := ResolveFile(request, fileSystem) + path := filepath.Join(rootpath, strings.Trim(request.Path, "/")) + if isPrivate(path) { + return nil + } + if isf, err := isFile(path); err != nil { + return gopher.Error(err).Response() + } else if !isf { + return nil + } + + if settings == nil { + settings = &gophermap.FileSystemSettings{} + } + + file, err := os.Open(path) if err != nil { return gopher.Error(err).Response() } - if file == nil { - return nil + if !(settings.ParseExtended && isMap(path, *settings)) { + return gopher.File(gopher.GuessItemType(path), file) + } + + defer func() { _ = file.Close() }() + + edoc, err := gophermap.ParseExtended(file, request.URL) + if err != nil { + return gopher.Error(err).Response() } - return gopher.File(gopher.GuessItemType(filepath), file) + doc, _, err := edoc.Compatible(filepath.Dir(path), *settings) + if err != nil { + return gopher.Error(err).Response() + } + return doc.Response() }) } @@ -34,95 +61,111 @@ func GopherFileHandler(fileSystem fs.FS) sr.Handler { // 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) sr.Handler { +func GopherDirectoryDefault(rootpath string, settings *gophermap.FileSystemSettings) sr.Handler { return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { - dirpath, dir, err := ResolveDirectory(request, fileSystem) - if err != nil { - return gopher.Error(err).Response() + path := filepath.Join(rootpath, strings.Trim(request.Path, "/")) + if isPrivate(path) { + return nil } - if dir == nil { + if isd, err := isDir(path); err != nil { + return gopher.Error(err).Response() + } else if !isd { return nil } - defer func() { _ = dir.Close() }() - _, file, err := ResolveDirectoryDefault(fileSystem, dirpath, dir, filenames) - if err != nil { - return gopher.Error(err).Response() + if settings == nil { + settings = &gophermap.FileSystemSettings{} } - if file == nil { - return nil + + for _, fname := range settings.DirMaps { + fpath := filepath.Join(path, fname) + if isf, err := isFile(fpath); err != nil { + return gopher.Error(err).Response() + } else if !isf { + continue + } + + file, err := os.Open(fpath) + if err != nil { + return gopher.Error(err).Response() + } + + if settings.ParseExtended { + defer func() { _ = file.Close() }() + + edoc, err := gophermap.ParseExtended(file, request.URL) + if err != nil { + return gopher.Error(err).Response() + } + + doc, _, err := edoc.Compatible(path, *settings) + if err != nil { + return gopher.Error(err).Response() + } + return doc.Response() + } else { + return gopher.File(gopher.MenuType, file) + } } - return gopher.File(gopher.MenuType, file) + return nil }) } // 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) sr.Handler { +func GopherDirectoryListing(rootpath string, settings *gophermap.FileSystemSettings) sr.Handler { return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { - dirpath, dir, err := ResolveDirectory(request, fileSystem) - if err != nil { - return gopher.Error(err).Response() + path := filepath.Join(rootpath, strings.Trim(request.Path, "/")) + if isPrivate(path) { + return nil } - if dir == nil { + if isd, err := isDir(path); err != nil { + return gopher.Error(err).Response() + } else if !isd { return nil } - defer func() { _ = dir.Close() }() - if tpl == nil { - tpl = DefaultGopherDirectoryList + if settings == nil { + settings = &gophermap.FileSystemSettings{} } - body, err := RenderDirectoryListing(dirpath, dir, tpl, request.Server) + doc, err := gophermap.ListDir(path, request.URL, *settings) if err != nil { return gopher.Error(err).Response() } - return gopher.File(gopher.MenuType, body) + return doc.Response() }) } -// 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(gopher.GuessItemType(filepath))}) - }, +func isDir(path string) (bool, error) { + info, err := os.Stat(path) + if err != nil { + if isNotFound(err) { + err = nil + } + return false, err + } + return info.IsDir() && info.Mode()&4 == 4, nil } -// 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", - ), - ), -) +func isFile(path string) (bool, error) { + info, err := os.Stat(path) + if err != nil { + if isNotFound(err) { + err = nil + } + return false, err + } + m := info.Mode() + + return m.IsRegular() && m&4 == 4, nil +} + +func isMap(path string, settings gophermap.FileSystemSettings) bool { + if strings.HasSuffix(path, ".gophermap") { + return true + } + return slices.Contains(settings.DirMaps, filepath.Base(path)) +} |