summaryrefslogtreecommitdiff
path: root/contrib/fs
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/fs')
-rw-r--r--contrib/fs/gopher.go183
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))
+}