// Copyright (C) 2022 Marius Schellenberger package srv import ( "errors" "io" stdfs "io/fs" "net/http" "os" "path" "runtime" "strings" "cachefs/pkg/fs" "github.com/go-logr/logr" ) func PrintStack() { os.Stderr.Write(Stack()) } func Stack() []byte { buf := make([]byte, 1024) for { n := runtime.Stack(buf, true) if n < len(buf) { return buf[:n] } buf = make([]byte, 2*len(buf)) } } 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 if !skipLog(r.RequestURI) { defer func() { if s.Status() == http.StatusPartialContent { return } fs.log.Info("access", "client", r.RemoteAddr, "method", r.Method, "status", s.Status(), "uri", r.RequestURI, ) }() } upath := r.URL.Path if !strings.HasPrefix(upath, "/") { upath = "/" + upath r.URL.Path = upath } p := path.Clean(upath) option := r.FormValue("o") if option == "debug" { PrintStack() } d, _, err := fs.fs.StatWithOffline(p) if err != nil { msg, code := toHTTPError(err) http.Error(w, msg, code) return } //option := r.FormValue("o") if option == "v" { err = video.Execute(w, r.URL.Path) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } return } if !d.IsDir() { if option == "p" || option == "s" { if option == "p" { fs.fs.Preload(p) } else if option == "s" { fs.fs.CancelPreload(p) } rdir := "/" if i := strings.LastIndex(p, "/"); i > 0 { rdir = p[:i] } http.Redirect(w, r, rdir+"#"+anchor(p), http.StatusFound) return } if option == "n" { fs.fs.NoCache.ServeHTTP(w, r) return } fs.h.ServeHTTP(w, r) return } i := &responseInterceptor{w: w} // TODO implement directory listing cache offline := true if offline { r.Header.Del("If-Modified-Since") r.Header.Del("Cache-Control") } fs.h.ServeHTTP(i, r) if offline { w.Header().Del("Last-Modified") } paths, err := i.GetPaths(p, fs.fs) 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 } return "500 Internal Server Error", http.StatusInternalServerError }