Index page and post pages ready
This commit is contained in:
128
vendor/github.com/a-h/gemini/filesystem.go
generated
vendored
Normal file
128
vendor/github.com/a-h/gemini/filesystem.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/a-h/gemini/log"
|
||||
)
|
||||
|
||||
type Dir string
|
||||
|
||||
// Open implements FileSystem using os.Open, opening files for reading rooted
|
||||
// and relative to the directory d.
|
||||
func (d Dir) Open(name string) (File, error) {
|
||||
dir := string(d)
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
|
||||
return os.Open(fullName)
|
||||
}
|
||||
|
||||
// A FileSystem implements access to a collection of named files.
|
||||
// The elements in a file path are separated by slash ('/', U+002F)
|
||||
// characters, regardless of host operating system convention.
|
||||
type FileSystem interface {
|
||||
Open(name string) (File, error)
|
||||
}
|
||||
|
||||
// A File is returned by a FileSystem's Open method and can be
|
||||
// served by the FileServer implementation.
|
||||
//
|
||||
// The methods should behave the same as those on an *os.File.
|
||||
type File interface {
|
||||
io.Closer
|
||||
io.Reader
|
||||
Readdir(count int) ([]os.FileInfo, error)
|
||||
Stat() (os.FileInfo, error)
|
||||
}
|
||||
|
||||
func DirectoryListingHandler(path string, f File) Handler {
|
||||
return HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||
files, err := f.Readdir(-1)
|
||||
if err != nil {
|
||||
log.Warn("DirectoryListingHandler: readdir failed", log.String("reason", err.Error()), log.String("path", r.URL.Path), log.String("url", r.URL.String()))
|
||||
w.SetHeader(CodeTemporaryFailure, "readdir failed")
|
||||
return
|
||||
}
|
||||
sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
|
||||
w.SetHeader(CodeSuccess, DefaultMIMEType)
|
||||
fmt.Fprintf(w, "# Index of %s\n\n", path)
|
||||
fmt.Fprintln(w, "=> ../")
|
||||
for _, ff := range files {
|
||||
name := ff.Name()
|
||||
if ff.IsDir() {
|
||||
name += "/"
|
||||
}
|
||||
url := url.URL{Path: name}
|
||||
fmt.Fprintf(w, "=> %v\n", url.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FileContentHandler(name string, f File) Handler {
|
||||
return HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||
mType := mime.TypeByExtension(path.Ext(name))
|
||||
if mType == "" {
|
||||
mType = DefaultMIMEType
|
||||
}
|
||||
w.SetHeader(CodeSuccess, mType)
|
||||
if _, err := io.Copy(w, f); err != nil {
|
||||
log.Error("FileContentHandler: failed to write file", err, log.String("fileName", name))
|
||||
panic("error returning file contents")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FileSystemHandler(fs FileSystem) Handler {
|
||||
return HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||
if strings.Contains(r.URL.Path, "..") {
|
||||
log.Warn("FileSystemHandler: possible directory traversal attack", log.String("path", r.URL.Path), log.String("url", r.URL.String()))
|
||||
BadRequest(w, r)
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(r.URL.Path, "/") {
|
||||
r.URL.Path = "/" + r.URL.Path
|
||||
}
|
||||
f, err := fs.Open(r.URL.Path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
NotFoundHandler().ServeGemini(w, r)
|
||||
return
|
||||
}
|
||||
log.Warn("FileSystemHandler: file open failed", log.String("reason", err.Error()), log.String("path", r.URL.Path), log.String("url", r.URL.String()))
|
||||
w.SetHeader(CodeTemporaryFailure, "file open failed")
|
||||
return
|
||||
}
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
log.Warn("FileSystemHandler: file stat failed", log.String("reason", err.Error()), log.String("path", r.URL.Path), log.String("url", r.URL.String()))
|
||||
w.SetHeader(CodeTemporaryFailure, "file stat failed")
|
||||
return
|
||||
}
|
||||
if stat.IsDir() {
|
||||
// Look for index.gmi first before listing contents.
|
||||
if !strings.HasSuffix(r.URL.Path, "/") {
|
||||
RedirectPermanentHandler(r.URL.Path+"/").ServeGemini(w, r)
|
||||
return
|
||||
}
|
||||
index, err := fs.Open(r.URL.Path + "index.gmi")
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
DirectoryListingHandler(r.URL.Path, f).ServeGemini(w, r)
|
||||
return
|
||||
}
|
||||
FileContentHandler("index.gmi", index).ServeGemini(w, r)
|
||||
return
|
||||
}
|
||||
FileContentHandler(stat.Name(), f).ServeGemini(w, r)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user