Compare commits

...
1 Commits
Author SHA1 Message Date
Graham Steffaniak f95fa49cf6 updated with fixes for main issues (#991) 2025-07-21 14:52:37 -05:00
12 changed files with 133 additions and 81 deletions

No files matched your search

+13
View File
@@ -2,6 +2,19 @@
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
## v0.7.16-beta
**Notes**:
- more server logging for uploads when debug logging is enabled
**BugFixes**:
- fix onlyoffice integration viewing bug (#990)
- fix uploading files with exec permissions (#984)
- fix redirect on no source path (#989)
- refresh file info on rename (#989)
- listing refreshes when uploads finish (#989)
- disable edit mode for certain onlyoffice files (#971)
## v0.7.15-beta
**New Features**:
+11 -15
View File
@@ -156,28 +156,24 @@ func RefreshIndex(source string, path string, isDir bool) error {
return idx.RefreshFileInfo(iteminfo.FileOptions{Path: path, IsDir: isDir})
}
func MoveResource(sourceIndex, destIndex, realsrc, realdst string) error {
func MoveResource(isSrcDir, isDestDir bool, sourceIndex, destIndex, realsrc, realdst string) error {
err := fileutils.MoveFile(realsrc, realdst)
if err != nil {
return err
}
err = RefreshIndex(sourceIndex, filepath.Dir(realsrc), true)
if err != nil {
return err
}
return RefreshIndex(destIndex, filepath.Dir(realdst), true)
go RefreshIndex(sourceIndex, realsrc, isSrcDir) //nolint:errcheck
go RefreshIndex(sourceIndex, realdst, isDestDir) //nolint:errcheck
return nil
}
func CopyResource(sourceIndex, destIndex, realsrc, realdst string) error {
func CopyResource(isSrcDir, isDestDir bool, sourceIndex, destIndex, realsrc, realdst string) error {
err := fileutils.CopyFile(realsrc, realdst)
if err != nil {
return err
}
err = RefreshIndex(sourceIndex, filepath.Dir(realsrc), true)
if err != nil {
return err
}
return RefreshIndex(destIndex, filepath.Dir(realdst), true)
go RefreshIndex(sourceIndex, realsrc, isSrcDir) //nolint:errcheck
go RefreshIndex(sourceIndex, realdst, isDestDir) //nolint:errcheck
return nil
}
func WriteDirectory(opts iteminfo.FileOptions) error {
@@ -187,7 +183,7 @@ func WriteDirectory(opts iteminfo.FileOptions) error {
}
realPath, _, _ := idx.GetRealPath(opts.Path)
// Ensure the parent directories exist
err := os.MkdirAll(realPath, 0775)
err := os.MkdirAll(realPath, fileutils.PermDir)
if err != nil {
return err
}
@@ -201,12 +197,12 @@ func WriteFile(opts iteminfo.FileOptions, in io.Reader) error {
}
dst, _, _ := idx.GetRealPath(opts.Path)
// Ensure the parent directories exist
err := os.MkdirAll(filepath.Dir(dst), 0775)
err := os.MkdirAll(filepath.Dir(dst), fileutils.PermDir)
if err != nil {
return err
}
// Open the file for writing (create if it doesn't exist, truncate if it does)
file, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
file, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileutils.PermFile)
if err != nil {
return err
}
+6 -3
View File
@@ -10,6 +10,9 @@ import (
"github.com/gtsteffaniak/go-logger/logger"
)
const PermFile = 0640
const PermDir = 0750
// MoveFile moves a file from src to dst.
// By default, the rename system call is used. If src and dst point to different volumes,
// the file copy is used as a fallback.
@@ -63,13 +66,13 @@ func copySingleFile(source, dest string) error {
defer src.Close()
// Create the destination directory if needed.
err = os.MkdirAll(filepath.Dir(dest), 0775) //nolint:gomnd
err = os.MkdirAll(filepath.Dir(dest), PermDir)
if err != nil {
return err
}
// Create the destination file.
dst, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) //nolint:gomnd
dst, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, PermFile)
if err != nil {
return err
}
@@ -97,7 +100,7 @@ func copySingleFile(source, dest string) error {
// copyDirectory handles copying directories recursively.
func copyDirectory(source, dest string) error {
// Create the destination directory.
err := os.MkdirAll(dest, 0775) //nolint:gomnd
err := os.MkdirAll(dest, PermDir)
if err != nil {
return err
}
+4 -2
View File
@@ -18,7 +18,7 @@ require (
github.com/spf13/afero v1.14.0
github.com/stretchr/testify v1.10.0
github.com/swaggo/http-swagger v1.3.4
github.com/swaggo/swag v1.16.4
github.com/swaggo/swag v1.16.5
golang.org/x/crypto v0.40.0
golang.org/x/image v0.29.0
golang.org/x/mod v0.26.0
@@ -238,7 +238,9 @@ require (
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/tools v0.35.0 // indirect
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
+8 -4
View File
@@ -659,8 +659,8 @@ github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/swaggo/swag v1.16.5 h1:nMf2fEV1TetMTJb4XzD0Lz7jFfKJmJKGTygEey8NSxM=
github.com/swaggo/swag v1.16.5/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8=
github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8=
github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=
@@ -1003,8 +1003,12 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+7 -2
View File
@@ -84,6 +84,11 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
if d.user.DarkMode {
theme = "dark"
}
canEdit := iteminfo.CanEditOnlyOffice(d.user.Permissions.Modify, fileType)
mode := "view"
if canEdit {
mode = "edit"
}
callbackURL := fmt.Sprintf("%v/api/onlyoffice/callback?path=%v&auth=%v", urlFirst, encodedPath, d.token)
clientConfig := map[string]interface{}{
"document": map[string]interface{}{
@@ -92,7 +97,7 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
"title": fileInfo.Name,
"url": url + "&auth=" + d.token,
"permissions": map[string]interface{}{
"edit": d.user.Permissions.Modify,
"edit": canEdit,
"download": true,
"print": true,
},
@@ -109,7 +114,7 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
"uiTheme": theme,
},
"lang": d.user.Locale,
"mode": "edit",
"mode": mode,
},
}
if settings.Config.Integrations.OnlyOffice.Secret != "" {
+49 -41
View File
@@ -15,6 +15,7 @@ import (
"strings"
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files"
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/fileutils"
"github.com/gtsteffaniak/filebrowser/backend/common/errors"
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
@@ -195,11 +196,13 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
}
idx := indexing.GetIndex(source)
if idx == nil {
logger.Debugf("source %s not found", source)
return http.StatusNotFound, fmt.Errorf("source %s not found", source)
}
realPath, _, _ := idx.GetRealPath(userscope, path)
isDir := strings.HasSuffix(path, "/")
// Directories creation on POST.
if strings.HasSuffix(path, "/") {
if isDir {
err = files.WriteDirectory(fileOpts)
if err != nil {
logger.Debugf("error writing directory: %v", err)
@@ -214,6 +217,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
var offset int64
offset, err = strconv.ParseInt(chunkOffsetStr, 10, 64)
if err != nil {
logger.Debugf("invalid chunk offset: %v", err)
return http.StatusBadRequest, fmt.Errorf("invalid chunk offset: %v", err)
}
@@ -221,6 +225,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
totalSizeStr := r.Header.Get("X-File-Total-Size")
totalSize, err = strconv.ParseInt(totalSizeStr, 10, 64)
if err != nil {
logger.Debugf("invalid total size: %v", err)
return http.StatusBadRequest, fmt.Errorf("invalid total size: %v", err)
}
// On the first chunk, check for conflicts or handle override
@@ -229,6 +234,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
fileInfo, err = files.FileInfoFaster(fileOpts)
if err == nil { // File exists
if r.URL.Query().Get("override") != "true" {
logger.Debugf("resource already exists: %v", fileInfo.RealPath)
logger.Debugf("Resource already exists: %v", fileInfo.RealPath)
return http.StatusConflict, nil
}
@@ -245,12 +251,14 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
tempFilePath := filepath.Join(settings.Config.Server.CacheDir, "uploads", uploadID)
if err = os.MkdirAll(filepath.Dir(tempFilePath), 0755); err != nil {
logger.Debugf("could not create temp dir: %v", err)
return http.StatusInternalServerError, fmt.Errorf("could not create temp dir: %v", err)
}
// Create or open the temporary file
var outFile *os.File
outFile, err = os.OpenFile(tempFilePath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
logger.Debugf("could not open temp file: %v", err)
return http.StatusInternalServerError, fmt.Errorf("could not open temp file: %v", err)
}
defer outFile.Close()
@@ -258,6 +266,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
// Seek to the correct offset to write the chunk
_, err = outFile.Seek(offset, 0)
if err != nil {
logger.Debugf("could not seek in temp file: %v", err)
return http.StatusInternalServerError, fmt.Errorf("could not seek in temp file: %v", err)
}
@@ -265,47 +274,26 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
var chunkSize int64
chunkSize, err = io.Copy(outFile, r.Body)
if err != nil {
logger.Debugf("could not write chunk to temp file: %v", err)
return http.StatusInternalServerError, fmt.Errorf("could not write chunk to temp file: %v", err)
}
// check if the file is complete
if (offset + chunkSize) >= totalSize {
// close file before renaming
// close file before moving
outFile.Close()
// Move the completed file from the temp location to the final destination
err = os.Rename(tempFilePath, fileOpts.Path)
err = fileutils.MoveFile(tempFilePath, realPath)
if err != nil {
// If rename fails (e.g., cross-device link), fall back to copy and delete.
var in *os.File
in, err = os.Open(tempFilePath)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not open temp file for copying: %v", err)
}
defer in.Close()
var out *os.File
out, err = os.Create(realPath)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not create destination file for copying: %v", err)
}
defer out.Close()
if _, err = io.Copy(out, in); err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not copy temp file to destination: %v", err)
}
// Manually close files before removing source
in.Close()
out.Close()
if err = os.Remove(tempFilePath); err != nil {
logger.Errorf("failed to remove temporary upload file: %s", tempFilePath)
}
logger.Debugf("could not move temp file to destination: %v", err)
return http.StatusInternalServerError, fmt.Errorf("could not move temp file to destination: %v", err)
}
go files.RefreshIndex(source, realPath, false) //nolint:errcheck
}
return http.StatusOK, nil
}
logger.Debugf("non-chunked upload logic for %s", realPath)
// Non-chunked upload logic (original code)
fileInfo, err := files.FileInfoFaster(fileOpts)
if err == nil {
@@ -442,7 +430,7 @@ func resourcePatchHandler(w http.ResponseWriter, r *http.Request, d *requestCont
return http.StatusNotFound, fmt.Errorf("source %s not found", dstIndex)
}
// check target dir exists
parentDir, _, err := idx.GetRealPath(userscopeDst, filepath.Dir(dst))
parentDir, isDstDir, err := idx.GetRealPath(userscopeDst, filepath.Dir(dst))
if err != nil {
logger.Debugf("Could not get real path for parent dir: %v %v %v", userscopeDst, filepath.Dir(dst), err)
return http.StatusNotFound, err
@@ -467,7 +455,16 @@ func resourcePatchHandler(w http.ResponseWriter, r *http.Request, d *requestCont
if overwrite && !d.user.Permissions.Modify {
return http.StatusForbidden, fmt.Errorf("forbidden: user does not have permission to overwrite file")
}
err = patchAction(r.Context(), action, realSrc, realDest, d, isSrcDir, srcIndex, dstIndex)
err = patchAction(r.Context(), patchActionParams{
action: action,
srcIndex: srcIndex,
dstIndex: dstIndex,
src: realSrc,
dst: realDest,
d: d,
isSrcDir: isSrcDir,
isDstDir: isDstDir,
})
if err != nil {
logger.Debugf("Could not run patch action. src=%v dst=%v err=%v", realSrc, realDest, err)
}
@@ -490,19 +487,30 @@ func addVersionSuffix(source string) string {
return source
}
func patchAction(ctx context.Context, action, src, dst string, d *requestContext, isSrcDir bool, srcIndex, destIndex string) error {
switch action {
type patchActionParams struct {
action string
srcIndex string
dstIndex string
src string
dst string
d *requestContext
isSrcDir bool
isDstDir bool
}
func patchAction(ctx context.Context, params patchActionParams) error {
switch params.action {
case "copy":
err := files.CopyResource(srcIndex, destIndex, src, dst)
err := files.CopyResource(params.isSrcDir, params.isDstDir, params.srcIndex, params.dstIndex, params.src, params.dst)
return err
case "rename", "move":
idx := indexing.GetIndex(srcIndex)
srcPath := idx.MakeIndexPath(src)
idx := indexing.GetIndex(params.srcIndex)
srcPath := idx.MakeIndexPath(params.src)
fileInfo, err := files.FileInfoFaster(iteminfo.FileOptions{
Path: srcPath,
Source: srcIndex,
IsDir: isSrcDir,
Modify: d.user.Permissions.Modify,
Source: params.srcIndex,
IsDir: params.isSrcDir,
Modify: params.d.user.Permissions.Modify,
Expand: false,
ReadHeader: false,
})
@@ -513,9 +521,9 @@ func patchAction(ctx context.Context, action, src, dst string, d *requestContext
// delete thumbnails
preview.DelThumbs(ctx, fileInfo)
return files.MoveResource(srcIndex, destIndex, src, dst)
return files.MoveResource(params.isSrcDir, params.isDstDir, params.srcIndex, params.dstIndex, params.src, params.dst)
default:
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
return fmt.Errorf("unsupported action %s: %w", params.action, errors.ErrInvalidRequestParams)
}
}
+13
View File
@@ -5,6 +5,7 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
@@ -412,3 +413,15 @@ func HasDocConvertableExtension(name, mimetype string) bool {
}
return false
}
var ONLYOFFICE_EDIT_FILE_EXTENSIONS = []string{"doc", "docx", "pptx", "xls", "xlsx", "csv", "txt", "rtf", "md"}
func CanEditOnlyOffice(modify bool, extention string) bool {
if !modify {
return false
}
if slices.Contains(ONLYOFFICE_EDIT_FILE_EXTENSIONS, strings.ToLower(extention)) {
return true
}
return false
}
+7 -10
View File
@@ -329,22 +329,19 @@ export const getters = {
return files.sort((a, b) => a.progress - b.progress)
},
fileViewingDisabled: filename => {
const ext = ' .' + getFileExtension(filename)
const ext = ' ' + getFileExtension(filename)
if (state.user.disableViewingExt) {
const disabledExts = ' ' + state.user.disableViewingExt.toLowerCase()
if (disabledExts.includes(ext.toLowerCase())) {
return true
}
}
if (!state.req?.onlyOfficeId) {
return false
if (state.req?.onlyOfficeId && state.user.disableOfficePreviewExt) {
const disabledExts = ' ' + state.user.disableOfficePreviewExt.toLowerCase()
if (disabledExts.includes(ext.toLowerCase())) {
return true
}
}
if (state.user.disableOfficePreviewExt) {
const disabledExts = ' ' + state.user.disableOfficePreviewExt.toLowerCase()
if (disabledExts.includes(ext.toLowerCase())) {
return false
}
}
return true
return false
}
}
+12 -1
View File
@@ -1,6 +1,6 @@
import { reactive } from "vue";
import { filesApi } from "@/api";
import { state } from "@/store";
import { state,mutations } from "@/store";
class UploadManager {
constructor() {
@@ -11,6 +11,7 @@ class UploadManager {
this.isPausedForConflict = false;
this.isOverallPaused = false;
this.onConflict = () => {}; // Callback for UI
this.hadActiveUploads = false; // Track if we've had active uploads
}
setOnConflict(handler) {
@@ -110,6 +111,14 @@ class UploadManager {
this.start(upload.id);
}
}
// Only reload when we transition from having active uploads to having none
const hasNoActiveOrPending = this.activeUploads === 0 && !this.hasPending();
if (this.hadActiveUploads && hasNoActiveOrPending) {
console.log("all uploads processed ", this.queue);
mutations.setReload(true);
this.hadActiveUploads = false; // Reset the flag
}
}
start(id) {
@@ -131,6 +140,7 @@ class UploadManager {
async startDirectoryUpload(upload) {
this.activeUploads++;
this.hadActiveUploads = true; // Mark that we've had active uploads
upload.status = "uploading";
try {
@@ -154,6 +164,7 @@ class UploadManager {
async startFileUpload(upload) {
this.activeUploads++;
this.hadActiveUploads = true; // Mark that we've had active uploads
upload.status = "uploading";
const chunkSize = state.user.fileLoading.uploadChunkSizeMb * 1024 * 1024;
+2 -2
View File
@@ -127,9 +127,9 @@ export default {
routePath == "/";
// lets redirect if multiple sources and user went to /files/
if (state.serverHasMultipleSources && rootRoute) {
const targetPath = `${routePath}/${state.sources.current}`;
const targetPath = `/files/${state.sources.current}`;
// Prevent infinite loop by checking if we're already at the target path
if (this.$route.path !== targetPath) {
if (routePath !== targetPath) {
router.push(targetPath);
return;
}
+1 -1
View File
@@ -163,7 +163,7 @@ export default {
// Try to split the HTML while preserving tags
const htmlLines: string[] = [];
let currentHTML = html;
let currentLineIndex = 0;
//let currentLineIndex = 0;
// For each line, try to extract the corresponding HTML
for (let i = 0; i < textLines.length; i++) {