1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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
}
|