package fstore import ( "embed" "io" "net/http" "os" "path/filepath" "git.crumpington.com/public/jldb/fstore/pages" ) //go:embed static/* var staticFS embed.FS type browser struct { store *Store } func (s *Store) ServeBrowser(listenAddr string) error { b := &browser{s} http.HandleFunc("/", b.handle) http.Handle("/static/", http.FileServer(http.FS(staticFS))) return http.ListenAndServe(listenAddr, nil) } func (b *browser) handle(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: b.handleGet(w, r) case http.MethodPost: b.handlePOST(w, r) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } } func (b *browser) handleGet(w http.ResponseWriter, r *http.Request) { path := cleanPath(r.URL.Path) if err := validatePath(path); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } info, err := b.store.Stat(path) if err != nil { if os.IsNotExist(err) { pages.Page{Path: path}.Render(w) return } http.Error(w, err.Error(), http.StatusNotFound) return } if !info.IsDir() { b.store.Serve(w, r, path) return } dirs, files, err := b.store.List(path) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } pages.Page{ Path: path, Dirs: dirs, Files: files, }.Render(w) } // Handle actions: // - upload (multipart), // - delete func (b *browser) handlePOST(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(1024 * 1024); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } switch r.Form.Get("Action") { case "Upload": b.handlePOSTUpload(w, r) case "Delete": b.handlePOSTDelete(w, r) default: http.Error(w, "unknown action", http.StatusBadRequest) } } func (b *browser) handlePOSTUpload(w http.ResponseWriter, r *http.Request) { file, handler, err := r.FormFile("File") if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } defer file.Close() relativePath := handler.Filename if p := r.Form.Get("Path"); p != "" { relativePath = p } fullPath := filepath.Join(r.URL.Path, relativePath) tmpPath := b.store.GetTempFilePath() defer os.RemoveAll(tmpPath) tmpFile, err := os.Create(tmpPath) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer tmpFile.Close() if _, err := io.Copy(tmpFile, file); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := b.store.Store(tmpPath, fullPath); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, filepath.Dir(fullPath), http.StatusSeeOther) } func (b *browser) handlePOSTDelete(w http.ResponseWriter, r *http.Request) { path := r.Form.Get("Path") if err := b.store.Remove(path); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, filepath.Dir(path), http.StatusSeeOther) }