fix: drop modify permission for uploading new file
parent
47b3e218ad
commit
6337c44525
|
@ -3,7 +3,6 @@ import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { useUploadStore } from "@/stores/upload";
|
import { useUploadStore } from "@/stores/upload";
|
||||||
import { removePrefix } from "@/api/utils";
|
import { removePrefix } from "@/api/utils";
|
||||||
import { fetchURL } from "./utils";
|
|
||||||
|
|
||||||
const RETRY_BASE_DELAY = 1000;
|
const RETRY_BASE_DELAY = 1000;
|
||||||
const RETRY_MAX_DELAY = 20000;
|
const RETRY_MAX_DELAY = 20000;
|
||||||
|
@ -28,8 +27,6 @@ export async function upload(
|
||||||
filePath = removePrefix(filePath);
|
filePath = removePrefix(filePath);
|
||||||
const resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`;
|
const resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`;
|
||||||
|
|
||||||
await createUpload(resourcePath);
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
// Exit early because of typescript, tus content can't be a string
|
// Exit early because of typescript, tus content can't be a string
|
||||||
|
@ -38,7 +35,7 @@ export async function upload(
|
||||||
}
|
}
|
||||||
return new Promise<void | string>((resolve, reject) => {
|
return new Promise<void | string>((resolve, reject) => {
|
||||||
const upload = new tus.Upload(content, {
|
const upload = new tus.Upload(content, {
|
||||||
uploadUrl: `${baseURL}${resourcePath}`,
|
endpoint: `${baseURL}${resourcePath}`,
|
||||||
chunkSize: tusSettings.chunkSize,
|
chunkSize: tusSettings.chunkSize,
|
||||||
retryDelays: computeRetryDelays(tusSettings),
|
retryDelays: computeRetryDelays(tusSettings),
|
||||||
parallelUploads: 1,
|
parallelUploads: 1,
|
||||||
|
@ -46,6 +43,18 @@ export async function upload(
|
||||||
headers: {
|
headers: {
|
||||||
"X-Auth": authStore.jwt,
|
"X-Auth": authStore.jwt,
|
||||||
},
|
},
|
||||||
|
onShouldRetry: function (err) {
|
||||||
|
const status = err.originalResponse
|
||||||
|
? err.originalResponse.getStatus()
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// Do not retry for file conflict.
|
||||||
|
if (status === 409) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
onError: function (error) {
|
onError: function (error) {
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||||
|
@ -92,17 +101,6 @@ export async function upload(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createUpload(resourcePath: string) {
|
|
||||||
const headResp = await fetchURL(resourcePath, {
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
if (headResp.status !== 201) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to create an upload: ${headResp.status} ${headResp.statusText}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeRetryDelays(tusSettings: TusSettings): number[] | undefined {
|
function computeRetryDelays(tusSettings: TusSettings): number[] | undefined {
|
||||||
if (!tusSettings.retryCount || tusSettings.retryCount < 1) {
|
if (!tusSettings.retryCount || tusSettings.retryCount < 1) {
|
||||||
// Disable retries altogether
|
// Disable retries altogether
|
||||||
|
|
|
@ -11,11 +11,36 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
"github.com/filebrowser/filebrowser/v2/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Tracks active uploads along with their respective upload lengths
|
||||||
|
var activeUploads sync.Map
|
||||||
|
|
||||||
|
func registerUpload(filePath string, fileSize int64) {
|
||||||
|
activeUploads.Store(filePath, fileSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unregisterUpload(filePath string) {
|
||||||
|
activeUploads.Delete(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActiveUploadLength(filePath string) (int64, error) {
|
||||||
|
value, exists := activeUploads.Load(filePath)
|
||||||
|
if !exists {
|
||||||
|
return 0, fmt.Errorf("no active upload found for the given path")
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.(int64), nil
|
||||||
|
}
|
||||||
|
|
||||||
func tusPostHandler() handleFunc {
|
func tusPostHandler() handleFunc {
|
||||||
return withUser(func(_ http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
|
if !d.user.Perm.Create {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
file, err := files.NewFileInfo(&files.FileOptions{
|
file, err := files.NewFileInfo(&files.FileOptions{
|
||||||
Fs: d.user.Fs,
|
Fs: d.user.Fs,
|
||||||
Path: r.URL.Path,
|
Path: r.URL.Path,
|
||||||
|
@ -41,25 +66,55 @@ func tusPostHandler() handleFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileFlags := os.O_CREATE | os.O_WRONLY
|
fileFlags := os.O_CREATE | os.O_WRONLY
|
||||||
if r.URL.Query().Get("override") == "true" {
|
|
||||||
fileFlags |= os.O_TRUNC
|
|
||||||
}
|
|
||||||
|
|
||||||
// if file exists
|
// if file exists
|
||||||
if file != nil {
|
if file != nil {
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
return http.StatusBadRequest, fmt.Errorf("cannot upload to a directory %s", file.RealPath())
|
return http.StatusBadRequest, fmt.Errorf("cannot upload to a directory %s", file.RealPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Existing files will remain untouched unless explicitly instructed to override
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fileFlags |= os.O_TRUNC
|
||||||
}
|
}
|
||||||
|
|
||||||
openFile, err := d.user.Fs.OpenFile(r.URL.Path, fileFlags, files.PermFile)
|
openFile, err := d.user.Fs.OpenFile(r.URL.Path, fileFlags, files.PermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
if err := openFile.Close(); err != nil {
|
defer openFile.Close()
|
||||||
|
|
||||||
|
file, err = files.NewFileInfo(&files.FileOptions{
|
||||||
|
Fs: d.user.Fs,
|
||||||
|
Path: r.URL.Path,
|
||||||
|
Modify: d.user.Perm.Modify,
|
||||||
|
Expand: false,
|
||||||
|
ReadHeader: false,
|
||||||
|
Checker: d,
|
||||||
|
Content: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadLength, err := getUploadLength(r)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, fmt.Errorf("invalid upload length: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enables the user to utilize the PATCH endpoint for uploading file data
|
||||||
|
registerUpload(file.RealPath(), uploadLength)
|
||||||
|
|
||||||
|
w.Header().Set("Location", "/api/tus/"+r.URL.Path)
|
||||||
|
|
||||||
return http.StatusCreated, nil
|
return http.StatusCreated, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -92,7 +147,7 @@ func tusHeadHandler() handleFunc {
|
||||||
|
|
||||||
func tusPatchHandler() handleFunc {
|
func tusPatchHandler() handleFunc {
|
||||||
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
if !d.user.Perm.Modify || !d.Check(r.URL.Path) {
|
if !d.user.Perm.Create || !d.Check(r.URL.Path) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
if r.Header.Get("Content-Type") != "application/offset+octet-stream" {
|
if r.Header.Get("Content-Type") != "application/offset+octet-stream" {
|
||||||
|
@ -101,7 +156,7 @@ func tusPatchHandler() handleFunc {
|
||||||
|
|
||||||
uploadOffset, err := getUploadOffset(r)
|
uploadOffset, err := getUploadOffset(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, fmt.Errorf("invalid upload offset: %w", err)
|
return http.StatusBadRequest, fmt.Errorf("invalid upload offset")
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := files.NewFileInfo(&files.FileOptions{
|
file, err := files.NewFileInfo(&files.FileOptions{
|
||||||
|
@ -120,6 +175,11 @@ func tusPatchHandler() handleFunc {
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadLength, err := getActiveUploadLength(file.RealPath())
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusForbidden, err
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case file.IsDir:
|
case file.IsDir:
|
||||||
return http.StatusBadRequest, fmt.Errorf("cannot upload to a directory %s", file.RealPath())
|
return http.StatusBadRequest, fmt.Errorf("cannot upload to a directory %s", file.RealPath())
|
||||||
|
@ -148,12 +208,25 @@ func tusPatchHandler() handleFunc {
|
||||||
return http.StatusInternalServerError, fmt.Errorf("could not write to file: %w", err)
|
return http.StatusInternalServerError, fmt.Errorf("could not write to file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Upload-Offset", strconv.FormatInt(uploadOffset+bytesWritten, 10))
|
newOffset := uploadOffset + bytesWritten
|
||||||
|
w.Header().Set("Upload-Offset", strconv.FormatInt(newOffset, 10))
|
||||||
|
|
||||||
|
if newOffset >= uploadLength {
|
||||||
|
unregisterUpload(file.RealPath())
|
||||||
|
}
|
||||||
|
|
||||||
return http.StatusNoContent, nil
|
return http.StatusNoContent, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUploadLength(r *http.Request) (int64, error) {
|
||||||
|
uploadOffset, err := strconv.ParseInt(r.Header.Get("Upload-Length"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("invalid upload length: %w", err)
|
||||||
|
}
|
||||||
|
return uploadOffset, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getUploadOffset(r *http.Request) (int64, error) {
|
func getUploadOffset(r *http.Request) (int64, error) {
|
||||||
uploadOffset, err := strconv.ParseInt(r.Header.Get("Upload-Offset"), 10, 64)
|
uploadOffset, err := strconv.ParseInt(r.Header.Get("Upload-Offset"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue