filebrowser/backend/http/resource.go

307 lines
7.6 KiB
Go
Raw Normal View History

package http
import (
2021-03-10 15:14:01 +00:00
"context"
"fmt"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"github.com/shirou/gopsutil/v3/disk"
2020-07-20 17:38:43 +00:00
2023-06-15 01:08:09 +00:00
"github.com/gtsteffaniak/filebrowser/errors"
"github.com/gtsteffaniak/filebrowser/files"
"github.com/gtsteffaniak/filebrowser/fileutils"
)
var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
2024-09-16 21:01:16 +00:00
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
2024-08-24 22:02:33 +00:00
if err != nil {
return http.StatusNotFound, err
}
file, err := files.FileInfoFaster(files.FileOptions{
2024-08-24 22:02:33 +00:00
Path: realPath,
2024-09-16 21:01:16 +00:00
IsDir: isDir,
Modify: d.user.Perm.Modify,
Expand: true,
ReadHeader: d.server.TypeDetectionByHeader,
Checker: d,
2024-02-10 00:13:02 +00:00
Content: r.URL.Query().Get("content") == "true",
})
if err != nil {
return errToStatus(err), err
}
2024-09-16 21:01:16 +00:00
if !file.IsDir {
if checksum := r.URL.Query().Get("checksum"); checksum != "" {
err := file.Checksum(checksum)
if err == errors.ErrInvalidOption {
return http.StatusBadRequest, nil
} else if err != nil {
return http.StatusInternalServerError, err
}
}
}
return renderJSON(w, r, file)
})
func resourceDeleteHandler(fileCache FileCache) handleFunc {
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if r.URL.Path == "/" || !d.user.Perm.Delete {
return http.StatusForbidden, nil
}
2024-09-16 21:01:16 +00:00
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
2024-08-24 22:02:33 +00:00
if err != nil {
return http.StatusNotFound, err
}
fileOpts := files.FileOptions{
Path: realPath,
2024-09-16 21:01:16 +00:00
IsDir: isDir,
Modify: d.user.Perm.Modify,
2021-04-23 12:04:02 +00:00
Expand: false,
ReadHeader: d.server.TypeDetectionByHeader,
Checker: d,
2024-08-24 22:02:33 +00:00
}
file, err := files.FileInfoFaster(fileOpts)
if err != nil {
return errToStatus(err), err
}
// delete thumbnails
2021-03-10 15:14:01 +00:00
err = delThumbs(r.Context(), fileCache, file)
if err != nil {
return errToStatus(err), err
}
2024-08-24 22:02:33 +00:00
err = files.DeleteFiles(realPath, fileOpts)
if err != nil {
return errToStatus(err), err
}
return http.StatusOK, nil
})
}
2021-03-10 15:14:01 +00:00
func resourcePostHandler(fileCache FileCache) handleFunc {
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Create || !d.Check(r.URL.Path) {
2021-03-10 15:14:01 +00:00
return http.StatusForbidden, nil
}
2024-09-16 21:01:16 +00:00
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
2024-08-24 22:02:33 +00:00
if err != nil {
return http.StatusNotFound, err
2021-03-10 15:14:01 +00:00
}
2024-08-24 22:02:33 +00:00
fileOpts := files.FileOptions{
Path: realPath,
2024-09-16 21:01:16 +00:00
IsDir: isDir,
2021-03-10 15:14:01 +00:00
Modify: d.user.Perm.Modify,
2021-04-23 12:04:02 +00:00
Expand: false,
2021-03-10 15:14:01 +00:00
ReadHeader: d.server.TypeDetectionByHeader,
Checker: d,
2024-08-24 22:02:33 +00:00
}
// Directories creation on POST.
if strings.HasSuffix(r.URL.Path, "/") {
err = files.WriteDirectory(fileOpts) // Assign to the existing `err` variable
if err != nil {
return errToStatus(err), err
}
return http.StatusOK, nil
}
file, err := files.FileInfoFaster(fileOpts)
2021-03-10 15:14:01 +00:00
if err == nil {
if r.URL.Query().Get("override") != "true" {
return http.StatusConflict, nil
}
// Permission for overwriting the file
if !d.user.Perm.Modify {
return http.StatusForbidden, nil
}
2021-03-10 15:14:01 +00:00
err = delThumbs(r.Context(), fileCache, file)
if err != nil {
return errToStatus(err), err
}
}
2024-08-24 22:02:33 +00:00
err = files.WriteFile(fileOpts, r.Body)
2021-03-10 15:14:01 +00:00
return errToStatus(err), err
})
}
var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Modify || !d.Check(r.URL.Path) {
return http.StatusForbidden, nil
}
// Only allow PUT for files.
if strings.HasSuffix(r.URL.Path, "/") {
return http.StatusMethodNotAllowed, nil
}
2024-09-16 21:01:16 +00:00
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
if err != nil {
2024-08-24 22:02:33 +00:00
return http.StatusNotFound, err
}
2024-08-24 22:02:33 +00:00
fileOpts := files.FileOptions{
Path: realPath,
2024-09-16 21:01:16 +00:00
IsDir: isDir,
2024-08-24 22:02:33 +00:00
Modify: d.user.Perm.Modify,
Expand: false,
ReadHeader: d.server.TypeDetectionByHeader,
Checker: d,
}
2024-08-24 22:02:33 +00:00
err = files.WriteFile(fileOpts, r.Body)
return errToStatus(err), err
})
2024-08-24 22:02:33 +00:00
// TODO fix and verify this function still works in tests
2021-04-14 15:20:38 +00:00
func resourcePatchHandler(fileCache FileCache) handleFunc {
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
src := r.URL.Path
dst := r.URL.Query().Get("destination")
action := r.URL.Query().Get("action")
dst, err := url.QueryUnescape(dst)
if !d.Check(src) || !d.Check(dst) {
return http.StatusForbidden, nil
}
if err != nil {
return errToStatus(err), err
}
if dst == "/" || src == "/" {
return http.StatusForbidden, nil
}
override := r.URL.Query().Get("override") == "true"
rename := r.URL.Query().Get("rename") == "true"
if !override && !rename {
2024-08-24 22:02:33 +00:00
if _, err = os.Stat(dst); err == nil {
2021-04-14 15:20:38 +00:00
return http.StatusConflict, nil
}
2021-04-14 15:20:38 +00:00
}
if rename {
2024-08-24 22:02:33 +00:00
dst = addVersionSuffix(dst)
2021-04-14 15:20:38 +00:00
}
// Permission for overwriting the file
if override && !d.user.Perm.Modify {
return http.StatusForbidden, nil
}
err = d.RunHook(func() error {
return patchAction(r.Context(), action, src, dst, d, fileCache)
2021-04-14 15:20:38 +00:00
}, action, src, dst, d.user)
return errToStatus(err), err
})
}
2020-07-20 17:38:43 +00:00
2024-08-24 22:02:33 +00:00
func addVersionSuffix(source string) string {
2020-07-20 17:38:43 +00:00
counter := 1
dir, name := path.Split(source)
2020-07-20 17:38:43 +00:00
ext := filepath.Ext(name)
base := strings.TrimSuffix(name, ext)
for {
2024-08-24 22:02:33 +00:00
if _, err := os.Stat(source); err != nil {
2020-07-20 17:38:43 +00:00
break
}
renamed := fmt.Sprintf("%s(%d)%s", base, counter, ext)
source = path.Join(dir, renamed)
2020-07-20 17:38:43 +00:00
counter++
}
return source
2020-07-20 17:38:43 +00:00
}
2021-03-10 15:14:01 +00:00
func delThumbs(ctx context.Context, fileCache FileCache, file *files.FileInfo) error {
for _, previewSizeName := range PreviewSizeNames() {
size, _ := ParsePreviewSize(previewSizeName)
if err := fileCache.Delete(ctx, previewCacheKey(file, size)); err != nil {
2021-03-10 15:14:01 +00:00
return err
}
}
return nil
}
func patchAction(ctx context.Context, action, src, dst string, d *data, fileCache FileCache) error {
switch action {
// TODO: use enum
case "copy":
if !d.user.Perm.Create {
return errors.ErrPermissionDenied
}
2024-08-24 22:02:33 +00:00
return fileutils.Copy(src, dst)
case "rename":
if !d.user.Perm.Rename {
return errors.ErrPermissionDenied
}
src = path.Clean("/" + src)
dst = path.Clean("/" + dst)
2024-09-16 21:01:16 +00:00
realDest, _, err := files.GetRealPath(d.user.Scope, dst)
2024-08-24 22:02:33 +00:00
if err != nil {
return err
}
2024-09-16 21:01:16 +00:00
realSrc, isDir, err := files.GetRealPath(d.user.Scope, src)
2024-08-24 22:02:33 +00:00
if err != nil {
return err
}
file, err := files.FileInfoFaster(files.FileOptions{
2024-08-24 22:02:33 +00:00
Path: realSrc,
2024-09-16 21:01:16 +00:00
IsDir: isDir,
Modify: d.user.Perm.Modify,
Expand: false,
ReadHeader: false,
Checker: d,
})
if err != nil {
return err
}
// delete thumbnails
err = delThumbs(ctx, fileCache, file)
if err != nil {
return err
}
2024-08-24 22:02:33 +00:00
return fileutils.MoveFile(realSrc, realDest)
default:
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
}
}
type DiskUsageResponse struct {
Total uint64 `json:"total"`
Used uint64 `json:"used"`
}
var diskUsage = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
2024-09-16 21:01:16 +00:00
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
2024-08-24 22:02:33 +00:00
if err != nil {
return http.StatusNotFound, err
}
file, err := files.FileInfoFaster(files.FileOptions{
2024-08-24 22:02:33 +00:00
Path: realPath,
2024-09-16 21:01:16 +00:00
IsDir: isDir,
Modify: d.user.Perm.Modify,
Expand: false,
ReadHeader: false,
Checker: d,
})
if err != nil {
return errToStatus(err), err
}
fPath := file.RealPath()
if !file.IsDir {
return renderJSON(w, r, &DiskUsageResponse{
Total: 0,
Used: 0,
})
}
usage, err := disk.UsageWithContext(r.Context(), fPath)
if err != nil {
return errToStatus(err), err
}
return renderJSON(w, r, &DiskUsageResponse{
Total: usage.Total,
Used: usage.Used,
})
})