initial commit

This commit is contained in:
ston1th 2022-03-13 18:49:27 +01:00
commit 3053f89410
39 changed files with 4567 additions and 0 deletions

76
pkg/srv/interceptor.go Normal file
View file

@ -0,0 +1,76 @@
// Copyright (C) 2022 Marius Schellenberger
package srv
import (
"bytes"
"encoding/xml"
"net/http"
"sort"
)
type dirContents struct {
AllPaths []string `xml:"a"`
Dirs []string `xml:"-"`
Files []string `xml:"-"`
}
type statusInterceptor struct {
w http.ResponseWriter
status int
}
func (s *statusInterceptor) Header() http.Header {
return s.w.Header()
}
func (s *statusInterceptor) Write(p []byte) (int, error) {
return s.w.Write(p)
}
func (s *statusInterceptor) WriteHeader(statusCode int) {
s.w.WriteHeader(statusCode)
s.status = statusCode
}
func (s *statusInterceptor) Status() int {
if s.status == 0 {
return http.StatusOK
}
return s.status
}
type responseInterceptor struct {
buf bytes.Buffer
w http.ResponseWriter
status int
}
func (r *responseInterceptor) Header() http.Header {
return r.w.Header()
}
func (r *responseInterceptor) Write(p []byte) (int, error) {
return r.buf.Write(p)
}
func (r *responseInterceptor) WriteHeader(statusCode int) {
r.status = statusCode
}
func (r *responseInterceptor) Status() int {
if r.status == 0 {
return http.StatusOK
}
return r.status
}
func (r *responseInterceptor) GetPaths() (dir dirContents, err error) {
err = xml.Unmarshal(r.buf.Bytes(), &dir)
if err != nil {
return
}
for _, p := range dir.AllPaths {
if p[len(p)-1] == '/' {
dir.Dirs = append(dir.Dirs, p)
} else {
dir.Files = append(dir.Files, p)
}
}
sort.Strings(dir.Dirs)
sort.Strings(dir.Files)
return
}

114
pkg/srv/srv.go Normal file
View file

@ -0,0 +1,114 @@
// Copyright (C) 2022 Marius Schellenberger
package srv
import (
"errors"
"io"
stdfs "io/fs"
"net/http"
"path"
"strings"
"cachefs/pkg/fs"
"github.com/go-logr/logr"
)
type FileServer struct {
log logr.Logger
fs *fs.FS
h http.Handler
}
func NewFileServer(fs *fs.FS, log logr.Logger) http.Handler {
return &FileServer{log, fs, http.FileServer(fs)}
}
func skipLog(path string) bool {
return strings.HasSuffix(path, "favicon.ico")
}
func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s := &statusInterceptor{w: w}
w = s
defer func() {
if skipLog(r.RequestURI) {
return
}
fs.log.Info("access",
"uri", r.RequestURI,
"method", r.Method,
"status", s.Status(),
)
}()
upath := r.URL.Path
if !strings.HasPrefix(upath, "/") {
upath = "/" + upath
r.URL.Path = upath
}
p := path.Clean(upath)
d, err := fs.fs.Stat(p)
if err != nil {
msg, code := toHTTPError(err)
http.Error(w, msg, code)
return
}
if r.FormValue("v") == "t" {
err = video.Execute(w, r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
if !d.IsDir() {
if r.FormValue("p") == "t" {
fs.fs.Preload(p)
idx := strings.LastIndex(p, "/")
if idx > 0 {
p = p[:idx]
} else {
p = "/"
}
http.Redirect(w, r, p, http.StatusFound)
return
}
if r.FormValue("n") == "t" {
fs.fs.NoCache.ServeHTTP(w, r)
return
}
fs.h.ServeHTTP(w, r)
return
}
i := &responseInterceptor{w: w}
fs.h.ServeHTTP(i, r)
paths, err := i.GetPaths()
if err == io.EOF {
w.WriteHeader(i.Status())
return
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(i.Status())
err = index.Execute(w, paths)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func toHTTPError(err error) (string, int) {
if errors.Is(err, stdfs.ErrNotExist) {
return "404 page not found", http.StatusNotFound
}
if errors.Is(err, stdfs.ErrPermission) {
return "403 Forbidden", http.StatusForbidden
}
// Default:
return "500 Internal Server Error", http.StatusInternalServerError
}

48
pkg/srv/templates.go Normal file
View file

@ -0,0 +1,48 @@
// Copyright (C) 2022 Marius Schellenberger
package srv
import "html/template"
var (
index = template.Must(template.New("index").Parse(`<!doctype html>
<html>
<head>
<style>
body { text-align: center; padding: 0 150px 0 150px; font: 20px Helvetica, sans-serif; color: #333; }
article { display: block; text-align: left; width: 650px; margin: 0 auto; }
table { text-align: left; }
</style>
</head>
<body>
<article>
<pre>
[v]: show video
[n]: skip file caching
[p]: preload file
</pre>
<table>
<tr><th>Path</th><th>Options</th></tr>
<tr><td><a href="../">../</a></td><td></td></tr>
{{range $s := .Dirs -}}
<tr><td><a href="{{$s}}">{{$s}}</a></td><td></td></tr>
{{end -}}
{{range $s := .Files -}}
<tr><td><a href="{{$s}}">{{$s}}</a></td>
<td><a href="{{$s}}?v=t">[v]</a>&nbsp;<a href="{{$s}}?n=t">[n]</a>&nbsp;<a href="{{$s}}?p=t">[p]</a></td></tr>
{{end -}}
</table>
</article>
</body>
</html>`))
video = template.Must(template.New("video").Parse(`<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width">
</head>
<body>
<video id="video" style="width: 100%; height: 100%;" src="{{.}}" controls=""></video>
<script>document.getElementById("video").volume = 0.5;</script>
</body>
</html>`))
)