diff --git a/docs/custom-actions.md b/docs/custom-actions.md
index faafb870..2592f049 100644
--- a/docs/custom-actions.md
+++ b/docs/custom-actions.md
@@ -28,9 +28,7 @@ For cloud backends directories are virtual, they are created implicitly when you
The notification will indicate if an error is detected and so, for example, a partial file is uploaded.
-The `pre-delete` action, if defined, will be called just before files deletion. If the external command completes with a zero exit status or the HTTP notification response code is `200`, SFTPGo will assume that the file was already deleted/moved and so it will not try to remove the file and it will not execute the hook defined for the `delete` action.
-
-The `pre-download` and `pre-upload` actions, will be called before downloads and uploads. If the external command completes with a zero exit status or the HTTP notification response code is `200`, SFTPGo will allow the operation, otherwise the client will get a permission denied error.
+The `pre-delete`, `pre-download` and `pre-upload` actions, will be called before deleting, downloading and uploading files. If the external command completes with a zero exit status or the HTTP notification response code is `200`, SFTPGo will allow the operation, otherwise the client will get a permission denied error.
If the `hook` defines a path to an external program, then this program can read the following environment variables:
diff --git a/docs/eventmanager.md b/docs/eventmanager.md
index 6b4b47d2..d783921f 100644
--- a/docs/eventmanager.md
+++ b/docs/eventmanager.md
@@ -63,7 +63,7 @@ Actions are executed in a sequential order except for sync actions that are exec
- `Stop on failure`, the next action will not be executed if the current one fails.
- `Failure action`, this action will be executed only if at least another one fails. :warning: Please note that a failure action isn't executed if the event fails, for example if a download fails the main action is executed. The failure action is executed only if one of the non-failure actions associated to a rule fails.
-- `Execute sync`, for upload events, you can execute the action(s) synchronously. Executing an action synchronously means that SFTPGo will not return a result code to the client (which is waiting for it) until your action have completed its execution. If your acion takes a long time to complete this could cause a timeout on the client side, which wouldn't receive the server response in a timely manner and eventually drop the connection. For pre-* events at least a sync action is required. If pre-delete sync action(s) completes successfully, SFTPGo will assume that the file was already deleted/moved and so it will not try to remove the file and it will not execute any defined `delete` actions. If pre-upload/download action(s) completes successfully, SFTPGo will allow the operation, otherwise the client will get a permission denied error.
+- `Execute sync`, for upload events, you can execute the action(s) synchronously. Executing an action synchronously means that SFTPGo will not return a result code to the client (which is waiting for it) until your action have completed its execution. If your acion takes a long time to complete this could cause a timeout on the client side, which wouldn't receive the server response in a timely manner and eventually drop the connection. For pre-* events at least a sync action is required. If pre-delete,pre-upload, pre-download sync action(s) completes successfully, SFTPGo will allow the operation, otherwise the client will get a permission denied error.
If you are running multiple SFTPGo instances connected to the same data provider, you can choose whether to allow simultaneous execution for scheduled actions.
diff --git a/go.mod b/go.mod
index 8bd61f44..76a508ea 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.17.0
github.com/aws/aws-sdk-go-v2/service/sts v1.17.7
+ github.com/bmatcuk/doublestar/v4 v4.4.0
github.com/cockroachdb/cockroach-go/v2 v2.2.20
github.com/coreos/go-oidc/v3 v3.4.0
github.com/drakkan/webdav v0.0.0-20221101181759-17ed21f9337b
diff --git a/go.sum b/go.sum
index 232f9236..b776c60a 100644
--- a/go.sum
+++ b/go.sum
@@ -304,6 +304,8 @@ github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/bmatcuk/doublestar/v4 v4.4.0 h1:LmAwNwhjEbYtyVLzjcP/XeVw4nhuScHGkF/XWXnvIic=
+github.com/bmatcuk/doublestar/v4 v4.4.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
diff --git a/internal/common/actions.go b/internal/common/actions.go
index fcb83e25..e932c690 100644
--- a/internal/common/actions.go
+++ b/internal/common/actions.go
@@ -41,8 +41,6 @@ import (
)
var (
- errUnconfiguredAction = errors.New("no hook is configured for this action")
- errNoHook = errors.New("unable to execute action, no hook defined")
errUnexpectedHTTResponse = errors.New("unexpected HTTP hook response code")
hooksConcurrencyGuard = make(chan struct{}, 150)
activeHooks atomic.Int32
@@ -80,24 +78,18 @@ func InitializeActionHandler(handler ActionHandler) {
actionHandler = handler
}
-func handleUnconfiguredPreAction(operation string) error {
- // for pre-delete we execute the internal handling on error, so we must return errUnconfiguredAction.
- // Other pre action will deny the operation on error so if we have no configuration we must return
- // a nil error
- if operation == operationPreDelete {
- return errUnconfiguredAction
- }
- return nil
-}
-
-// ExecutePreAction executes a pre-* action and returns the result
-func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath string, fileSize int64, openFlags int) error {
+// ExecutePreAction executes a pre-* action and returns the result.
+// The returned status has the following meaning:
+// - 0 not executed
+// - 1 executed using an external hook
+// - 2 executed using the event manager
+func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath string, fileSize int64, openFlags int) (int, error) {
var event *notifier.FsEvent
hasNotifiersPlugin := plugin.Handler.HasNotifiers()
hasHook := util.Contains(Config.Actions.ExecuteOn, operation)
hasRules := eventManager.hasFsRules()
if !hasHook && !hasNotifiersPlugin && !hasRules {
- return handleUnconfiguredPreAction(operation)
+ return 0, nil
}
event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "",
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil))
@@ -124,11 +116,11 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
}
executedSync, err := eventManager.handleFsEvent(params)
if executedSync {
- return err
+ return 2, err
}
}
if !hasHook {
- return handleUnconfiguredPreAction(operation)
+ return 0, nil
}
return actionHandler.Handle(event)
}
@@ -176,7 +168,8 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
}
if hasHook {
if util.Contains(Config.Actions.ExecuteSync, operation) {
- return actionHandler.Handle(notification)
+ _, err := actionHandler.Handle(notification)
+ return err
}
go func() {
startNewHook()
@@ -190,7 +183,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
// ActionHandler handles a notification for a Protocol Action.
type ActionHandler interface {
- Handle(notification *notifier.FsEvent) error
+ Handle(notification *notifier.FsEvent) (int, error)
}
func newActionNotification(
@@ -244,28 +237,30 @@ func newActionNotification(
type defaultActionHandler struct{}
-func (h *defaultActionHandler) Handle(event *notifier.FsEvent) error {
+func (h *defaultActionHandler) Handle(event *notifier.FsEvent) (int, error) {
if !util.Contains(Config.Actions.ExecuteOn, event.Action) {
- return errUnconfiguredAction
+ return 0, nil
}
if Config.Actions.Hook == "" {
logger.Warn(event.Protocol, "", "Unable to send notification, no hook is defined")
- return errNoHook
+ return 0, nil
}
if strings.HasPrefix(Config.Actions.Hook, "http") {
- return h.handleHTTP(event)
+ err := h.handleHTTP(event)
+ return 1, err
}
- return h.handleCommand(event)
+ err := h.handleCommand(event)
+ return 1, err
}
func (h *defaultActionHandler) handleHTTP(event *notifier.FsEvent) error {
u, err := url.Parse(Config.Actions.Hook)
if err != nil {
- logger.Error(event.Protocol, "", "Invalid hook %#v for operation %#v: %v",
+ logger.Error(event.Protocol, "", "Invalid hook %q for operation %q: %v",
Config.Actions.Hook, event.Action, err)
return err
}
@@ -294,7 +289,7 @@ func (h *defaultActionHandler) handleHTTP(event *notifier.FsEvent) error {
func (h *defaultActionHandler) handleCommand(event *notifier.FsEvent) error {
if !filepath.IsAbs(Config.Actions.Hook) {
- err := fmt.Errorf("invalid notification command %#v", Config.Actions.Hook)
+ err := fmt.Errorf("invalid notification command %q", Config.Actions.Hook)
logger.Warn(event.Protocol, "", "unable to execute notification command: %v", err)
return err
diff --git a/internal/common/actions_test.go b/internal/common/actions_test.go
index 1d37c7dc..ddebe493 100644
--- a/internal/common/actions_test.go
+++ b/internal/common/actions_test.go
@@ -136,18 +136,21 @@ func TestActionHTTP(t *testing.T) {
}
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "",
xid.New().String(), 123, 0, 1)
- err := actionHandler.Handle(a)
+ status, err := actionHandler.Handle(a)
assert.NoError(t, err)
+ assert.Equal(t, 1, status)
Config.Actions.Hook = "http://invalid:1234"
- err = actionHandler.Handle(a)
+ status, err = actionHandler.Handle(a)
assert.Error(t, err)
+ assert.Equal(t, 1, status)
Config.Actions.Hook = fmt.Sprintf("http://%v/404", httpAddr)
- err = actionHandler.Handle(a)
+ status, err = actionHandler.Handle(a)
if assert.Error(t, err) {
assert.EqualError(t, err, errUnexpectedHTTResponse.Error())
}
+ assert.Equal(t, 1, status)
Config.Actions = actionsCopy
}
@@ -173,8 +176,9 @@ func TestActionCMD(t *testing.T) {
sessionID := shortuuid.New()
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
123, 0, 1)
- err = actionHandler.Handle(a)
+ status, err := actionHandler.Handle(a)
assert.NoError(t, err)
+ assert.Equal(t, 1, status)
c := NewBaseConnection("id", ProtocolSFTP, "", "", *user)
err = ExecuteActionNotification(c, OperationSSHCmd, "path", "vpath", "target", "vtarget", "sha1sum", 0, nil)
@@ -205,29 +209,32 @@ func TestWrongActions(t *testing.T) {
a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(),
123, 0, 1)
- err := actionHandler.Handle(a)
+ status, err := actionHandler.Handle(a)
assert.Error(t, err, "action with bad command must fail")
+ assert.Equal(t, 1, status)
a.Action = operationDelete
- err = actionHandler.Handle(a)
- assert.EqualError(t, err, errUnconfiguredAction.Error())
+ status, err = actionHandler.Handle(a)
+ assert.NoError(t, err)
+ assert.Equal(t, 0, status)
Config.Actions.Hook = "http://foo\x7f.com/"
a.Action = operationUpload
- err = actionHandler.Handle(a)
+ status, err = actionHandler.Handle(a)
assert.Error(t, err, "action with bad url must fail")
+ assert.Equal(t, 1, status)
Config.Actions.Hook = ""
- err = actionHandler.Handle(a)
- if assert.Error(t, err) {
- assert.EqualError(t, err, errNoHook.Error())
- }
+ status, err = actionHandler.Handle(a)
+ assert.NoError(t, err)
+ assert.Equal(t, 0, status)
Config.Actions.Hook = "relative path"
- err = actionHandler.Handle(a)
+ status, err = actionHandler.Handle(a)
if assert.Error(t, err) {
- assert.EqualError(t, err, fmt.Sprintf("invalid notification command %#v", Config.Actions.Hook))
+ assert.EqualError(t, err, fmt.Sprintf("invalid notification command %q", Config.Actions.Hook))
}
+ assert.Equal(t, 1, status)
Config.Actions = actionsCopy
}
@@ -242,7 +249,7 @@ func TestPreDeleteAction(t *testing.T) {
assert.NoError(t, err)
Config.Actions = ProtocolActions{
ExecuteOn: []string{operationPreDelete},
- Hook: hookCmd,
+ Hook: "missing hook",
}
homeDir := filepath.Join(os.TempDir(), "test_user")
err = os.MkdirAll(homeDir, os.ModePerm)
@@ -264,8 +271,12 @@ func TestPreDeleteAction(t *testing.T) {
info, err := os.Stat(testfile)
assert.NoError(t, err)
err = c.RemoveFile(fs, testfile, "testfile", info)
- assert.NoError(t, err)
+ assert.ErrorIs(t, err, c.GetPermissionDeniedError())
assert.FileExists(t, testfile)
+ Config.Actions.Hook = hookCmd
+ err = c.RemoveFile(fs, testfile, "testfile", info)
+ assert.NoError(t, err)
+ assert.NoFileExists(t, testfile)
os.RemoveAll(homeDir)
@@ -289,10 +300,12 @@ func TestUnconfiguredHook(t *testing.T) {
assert.True(t, plugin.Handler.HasNotifiers())
c := NewBaseConnection("id", ProtocolSFTP, "", "", dataprovider.User{})
- err = ExecutePreAction(c, OperationPreDownload, "", "", 0, 0)
+ status, err := ExecutePreAction(c, OperationPreDownload, "", "", 0, 0)
assert.NoError(t, err)
- err = ExecutePreAction(c, operationPreDelete, "", "", 0, 0)
- assert.ErrorIs(t, err, errUnconfiguredAction)
+ assert.Equal(t, status, 0)
+ status, err = ExecutePreAction(c, operationPreDelete, "", "", 0, 0)
+ assert.NoError(t, err)
+ assert.Equal(t, status, 0)
err = ExecuteActionNotification(c, operationDownload, "", "", "", "", "", 0, nil)
assert.NoError(t, err)
@@ -308,10 +321,10 @@ type actionHandlerStub struct {
called bool
}
-func (h *actionHandlerStub) Handle(event *notifier.FsEvent) error {
+func (h *actionHandlerStub) Handle(event *notifier.FsEvent) (int, error) {
h.called = true
- return nil
+ return 1, nil
}
func TestInitializeActionHandler(t *testing.T) {
@@ -322,8 +335,8 @@ func TestInitializeActionHandler(t *testing.T) {
InitializeActionHandler(&defaultActionHandler{})
})
- err := actionHandler.Handle(¬ifier.FsEvent{})
-
+ status, err := actionHandler.Handle(¬ifier.FsEvent{})
assert.NoError(t, err)
assert.True(t, handler.called)
+ assert.Equal(t, 1, status)
}
diff --git a/internal/common/connection.go b/internal/common/connection.go
index d20592ee..064d319e 100644
--- a/internal/common/connection.go
+++ b/internal/common/connection.go
@@ -391,11 +391,18 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
}
size := info.Size()
- actionErr := ExecutePreAction(c, operationPreDelete, fsPath, virtualPath, size, 0)
- if actionErr == nil {
- c.Log(logger.LevelDebug, "remove for path %q handled by pre-delete action", fsPath)
- } else {
- if err := fs.Remove(fsPath, false); err != nil {
+ status, err := ExecutePreAction(c, operationPreDelete, fsPath, virtualPath, size, 0)
+ if err != nil {
+ c.Log(logger.LevelDebug, "delete for file %q denied by pre action: %v", virtualPath, err)
+ return c.GetPermissionDeniedError()
+ }
+ updateQuota := true
+ if err := fs.Remove(fsPath, false); err != nil {
+ if status > 0 && fs.IsNotExist(err) {
+ // file removed in the pre-action, if the file was deleted from the EventManager the quota is already updated
+ c.Log(logger.LevelDebug, "file deleted from the hook, status: %d", status)
+ updateQuota = (status == 1)
+ } else {
c.Log(logger.LevelError, "failed to remove file/symlink %q: %+v", fsPath, err)
return c.GetFsError(fs, err)
}
@@ -403,7 +410,7 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
logger.CommandLog(removeLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1,
c.localAddr, c.remoteAddr)
- if info.Mode()&os.ModeSymlink == 0 {
+ if updateQuota && info.Mode()&os.ModeSymlink == 0 {
vfolder, err := c.User.GetVirtualFolderForPath(path.Dir(virtualPath))
if err == nil {
dataprovider.UpdateVirtualFolderQuota(&vfolder.BaseVirtualFolder, -1, -size, false) //nolint:errcheck
@@ -414,9 +421,7 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
dataprovider.UpdateUserQuota(&c.User, -1, -size, false) //nolint:errcheck
}
}
- if actionErr != nil {
- ExecuteActionNotification(c, operationDelete, fsPath, virtualPath, "", "", "", size, nil) //nolint:errcheck
- }
+ ExecuteActionNotification(c, operationDelete, fsPath, virtualPath, "", "", "", size, nil) //nolint:errcheck
return nil
}
diff --git a/internal/common/eventmanager.go b/internal/common/eventmanager.go
index f3575b35..88e60298 100644
--- a/internal/common/eventmanager.go
+++ b/internal/common/eventmanager.go
@@ -36,6 +36,7 @@ import (
"sync/atomic"
"time"
+ "github.com/bmatcuk/doublestar/v4"
"github.com/klauspost/compress/zip"
"github.com/robfig/cron/v3"
"github.com/rs/xid"
@@ -285,9 +286,7 @@ func (r *eventRulesContainer) checkFsEventMatch(conditions dataprovider.EventCon
return false
}
if !checkEventConditionPatterns(params.VirtualPath, conditions.Options.FsPaths) {
- if !checkEventConditionPatterns(params.ObjectName, conditions.Options.FsPaths) {
- return false
- }
+ return false
}
if len(conditions.Options.Protocols) > 0 && !util.Contains(conditions.Options.Protocols, params.Protocol) {
return false
@@ -966,7 +965,13 @@ func replaceWithReplacer(input string, replacer *strings.Replacer) string {
}
func checkEventConditionPattern(p dataprovider.ConditionPattern, name string) bool {
- matched, err := path.Match(p.Pattern, name)
+ var matched bool
+ var err error
+ if strings.Contains(p.Pattern, "**") {
+ matched, err = doublestar.Match(p.Pattern, name)
+ } else {
+ matched, err = path.Match(p.Pattern, name)
+ }
if err != nil {
eventManagerLog(logger.LevelError, "pattern matching error %q, err: %v", p.Pattern, err)
return false
diff --git a/internal/common/eventmanager_test.go b/internal/common/eventmanager_test.go
index b096e055..e43ef131 100644
--- a/internal/common/eventmanager_test.go
+++ b/internal/common/eventmanager_test.go
@@ -119,7 +119,7 @@ func TestEventRuleMatch(t *testing.T) {
},
FsPaths: []dataprovider.ConditionPattern{
{
- Pattern: "*.txt",
+ Pattern: "/**/*.txt",
},
},
Protocols: []string{ProtocolSFTP},
@@ -268,6 +268,40 @@ func TestEventRuleMatch(t *testing.T) {
assert.False(t, res)
}
+func TestDoubleStarMatching(t *testing.T) {
+ c := dataprovider.ConditionPattern{
+ Pattern: "/mydir/**",
+ }
+ res := checkEventConditionPattern(c, "/mydir")
+ assert.True(t, res)
+ res = checkEventConditionPattern(c, "/mydirname")
+ assert.False(t, res)
+ res = checkEventConditionPattern(c, "/mydir/sub")
+ assert.True(t, res)
+ res = checkEventConditionPattern(c, "/mydir/sub/dir")
+ assert.True(t, res)
+
+ c.Pattern = "/**/*"
+ res = checkEventConditionPattern(c, "/mydir")
+ assert.True(t, res)
+ res = checkEventConditionPattern(c, "/mydirname")
+ assert.True(t, res)
+ res = checkEventConditionPattern(c, "/mydir/sub/dir/file.txt")
+ assert.True(t, res)
+
+ c.Pattern = "/mydir/**/*.txt"
+ res = checkEventConditionPattern(c, "/mydir")
+ assert.False(t, res)
+ res = checkEventConditionPattern(c, "/mydirname/f.txt")
+ assert.False(t, res)
+ res = checkEventConditionPattern(c, "/mydir/sub")
+ assert.False(t, res)
+ res = checkEventConditionPattern(c, "/mydir/sub/dir")
+ assert.False(t, res)
+ res = checkEventConditionPattern(c, "/mydir/sub/dir/a.txt")
+ assert.True(t, res)
+}
+
func TestEventManager(t *testing.T) {
startEventScheduler()
action := &dataprovider.BaseEventAction{
diff --git a/internal/common/protocol_test.go b/internal/common/protocol_test.go
index 56e5bf97..b77a4139 100644
--- a/internal/common/protocol_test.go
+++ b/internal/common/protocol_test.go
@@ -3470,7 +3470,7 @@ func TestEventRule(t *testing.T) {
Pattern: "/subdir/*.dat",
},
{
- Pattern: "*.txt",
+ Pattern: "/**/*.txt",
},
},
},
@@ -3520,7 +3520,7 @@ func TestEventRule(t *testing.T) {
Options: dataprovider.ConditionOptions{
FsPaths: []dataprovider.ConditionPattern{
{
- Pattern: "*.dat",
+ Pattern: "/**/*.dat",
},
},
},
@@ -4231,6 +4231,14 @@ func TestEventRulePreDelete(t *testing.T) {
Trigger: dataprovider.EventTriggerFsEvent,
Conditions: dataprovider.EventConditions{
FsEvents: []string{"pre-delete"},
+ Options: dataprovider.ConditionOptions{
+ FsPaths: []dataprovider.ConditionPattern{
+ {
+ Pattern: fmt.Sprintf("/%s/**", movePath),
+ InverseMatch: true,
+ },
+ },
+ },
},
Actions: []dataprovider.EventAction{
{
@@ -4255,7 +4263,19 @@ func TestEventRulePreDelete(t *testing.T) {
}
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
assert.NoError(t, err)
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
+ u := getTestUser()
+ u.QuotaFiles = 1000
+ u.VirtualFolders = []vfs.VirtualFolder{
+ {
+ BaseVirtualFolder: vfs.BaseVirtualFolder{
+ Name: movePath,
+ MappedPath: filepath.Join(os.TempDir(), movePath),
+ },
+ VirtualPath: "/" + movePath,
+ QuotaFiles: 1000,
+ },
+ }
+ user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user)
if assert.NoError(t, err) {
@@ -4273,7 +4293,7 @@ func TestEventRulePreDelete(t *testing.T) {
assert.NoError(t, err)
err = client.Remove(path.Join(testDir, testFileName))
assert.NoError(t, err)
- // check
+ // check files
_, err = client.Stat(testFileName)
assert.ErrorIs(t, err, os.ErrNotExist)
_, err = client.Stat(path.Join(testDir, testFileName))
@@ -4282,6 +4302,23 @@ func TestEventRulePreDelete(t *testing.T) {
assert.NoError(t, err)
_, err = client.Stat(path.Join("/", movePath, testDir, testFileName))
assert.NoError(t, err)
+ // check quota
+ user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
+ assert.NoError(t, err)
+ assert.Equal(t, 0, user.UsedQuotaFiles)
+ assert.Equal(t, int64(0), user.UsedQuotaSize)
+ folder, _, err := httpdtest.GetFolderByName(movePath, http.StatusOK)
+ assert.NoError(t, err)
+ assert.Equal(t, 2, folder.UsedQuotaFiles)
+ assert.Equal(t, int64(200), folder.UsedQuotaSize)
+ // pre-delete action is not executed in movePath
+ err = client.Remove(path.Join("/", movePath, testFileName))
+ assert.NoError(t, err)
+ // check quota
+ folder, _, err = httpdtest.GetFolderByName(movePath, http.StatusOK)
+ assert.NoError(t, err)
+ assert.Equal(t, 1, folder.UsedQuotaFiles)
+ assert.Equal(t, int64(100), folder.UsedQuotaSize)
}
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
@@ -4294,6 +4331,10 @@ func TestEventRulePreDelete(t *testing.T) {
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
+ _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: movePath}, http.StatusOK)
+ assert.NoError(t, err)
+ err = os.RemoveAll(filepath.Join(os.TempDir(), movePath))
+ assert.NoError(t, err)
}
func TestEventRulePreDownloadUpload(t *testing.T) {
diff --git a/internal/ftpd/handler.go b/internal/ftpd/handler.go
index 216f4607..8ea1032a 100644
--- a/internal/ftpd/handler.go
+++ b/internal/ftpd/handler.go
@@ -342,7 +342,7 @@ func (c *Connection) downloadFile(fs vfs.Fs, fsPath, ftpPath string, offset int6
return nil, c.GetErrorForDeniedFile(policy)
}
- if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, fsPath, ftpPath, 0, 0); err != nil {
+ if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, fsPath, ftpPath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", ftpPath, err)
return nil, c.GetPermissionDeniedError()
}
@@ -404,7 +404,7 @@ func (c *Connection) handleFTPUploadToNewFile(fs vfs.Fs, flags int, resolvedPath
c.Log(logger.LevelInfo, "denying file write due to quota limits")
return nil, ftpserver.ErrStorageExceeded
}
- if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
+ if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, fmt.Errorf("%w, denied by pre-upload action", ftpserver.ErrFileNameNotAllowed)
}
@@ -449,7 +449,7 @@ func (c *Connection) handleFTPUploadToExistingFile(fs vfs.Fs, flags int, resolve
c.Log(logger.LevelDebug, "unable to get max write size: %v", err)
return nil, err
}
- if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, flags); err != nil {
+ if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, flags); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, fmt.Errorf("%w, denied by pre-upload action", ftpserver.ErrFileNameNotAllowed)
}
diff --git a/internal/httpd/handler.go b/internal/httpd/handler.go
index 48fd27e1..e61510c1 100644
--- a/internal/httpd/handler.go
+++ b/internal/httpd/handler.go
@@ -118,7 +118,7 @@ func (c *Connection) getFileReader(name string, offset int64, method string) (io
}
if method != http.MethodHead {
- if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, p, name, 0, 0); err != nil {
+ if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, p, name, 0, 0); err != nil {
c.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", name, err)
return nil, c.GetPermissionDeniedError()
}
@@ -193,7 +193,7 @@ func (c *Connection) handleUploadFile(fs vfs.Fs, resolvedPath, filePath, request
c.Log(logger.LevelInfo, "denying file write due to quota limits")
return nil, common.ErrQuotaExceeded
}
- err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, os.O_TRUNC)
+ _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, os.O_TRUNC)
if err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, c.GetPermissionDeniedError()
diff --git a/internal/sftpd/handler.go b/internal/sftpd/handler.go
index 60e076a7..f2df3bb6 100644
--- a/internal/sftpd/handler.go
+++ b/internal/sftpd/handler.go
@@ -93,7 +93,7 @@ func (c *Connection) Fileread(request *sftp.Request) (io.ReaderAt, error) {
return nil, err
}
- if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, p, request.Filepath, 0, 0); err != nil {
+ if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, p, request.Filepath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", request.Filepath, err)
return nil, c.GetPermissionDeniedError()
}
@@ -401,7 +401,7 @@ func (c *Connection) handleSFTPUploadToNewFile(fs vfs.Fs, pflags sftp.FileOpenFl
return nil, c.GetQuotaExceededError()
}
- if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
+ if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, c.GetPermissionDeniedError()
}
@@ -449,7 +449,7 @@ func (c *Connection) handleSFTPUploadToExistingFile(fs vfs.Fs, pflags sftp.FileO
return nil, err
}
- if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, osFlags); err != nil {
+ if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, osFlags); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, c.GetPermissionDeniedError()
}
diff --git a/internal/sftpd/scp.go b/internal/sftpd/scp.go
index 8b54dac6..b00abf85 100644
--- a/internal/sftpd/scp.go
+++ b/internal/sftpd/scp.go
@@ -236,7 +236,7 @@ func (c *scpCommand) handleUploadFile(fs vfs.Fs, resolvedPath, filePath string,
c.sendErrorMessage(nil, err)
return err
}
- err := common.ExecutePreAction(c.connection.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath,
+ _, err := common.ExecutePreAction(c.connection.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath,
fileSize, os.O_TRUNC)
if err != nil {
c.connection.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
@@ -532,7 +532,7 @@ func (c *scpCommand) handleDownload(filePath string) error {
return common.ErrPermissionDenied
}
- if err := common.ExecutePreAction(c.connection.BaseConnection, common.OperationPreDownload, p, filePath, 0, 0); err != nil {
+ if _, err := common.ExecutePreAction(c.connection.BaseConnection, common.OperationPreDownload, p, filePath, 0, 0); err != nil {
c.connection.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", filePath, err)
c.sendErrorMessage(fs, common.ErrPermissionDenied)
return common.ErrPermissionDenied
diff --git a/internal/webdavd/file.go b/internal/webdavd/file.go
index f6ffeaf8..b8a8ae39 100644
--- a/internal/webdavd/file.go
+++ b/internal/webdavd/file.go
@@ -169,7 +169,7 @@ func (f *webDavFile) checkFirstRead() error {
f.Connection.Log(logger.LevelWarn, "reading file %#v is not allowed", f.GetVirtualPath())
return f.Connection.GetErrorForDeniedFile(policy)
}
- err := common.ExecutePreAction(f.Connection, common.OperationPreDownload, f.GetFsPath(), f.GetVirtualPath(), 0, 0)
+ _, err := common.ExecutePreAction(f.Connection, common.OperationPreDownload, f.GetFsPath(), f.GetVirtualPath(), 0, 0)
if err != nil {
f.Connection.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", f.GetVirtualPath(), err)
return f.Connection.GetPermissionDeniedError()
diff --git a/internal/webdavd/handler.go b/internal/webdavd/handler.go
index 841353cf..a70d62e3 100644
--- a/internal/webdavd/handler.go
+++ b/internal/webdavd/handler.go
@@ -211,7 +211,7 @@ func (c *Connection) handleUploadToNewFile(fs vfs.Fs, resolvedPath, filePath, re
c.Log(logger.LevelInfo, "denying file write due to quota limits")
return nil, common.ErrQuotaExceeded
}
- if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
+ if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, c.GetPermissionDeniedError()
}
@@ -243,7 +243,7 @@ func (c *Connection) handleUploadToExistingFile(fs vfs.Fs, resolvedPath, filePat
c.Log(logger.LevelInfo, "denying file write due to quota limits")
return nil, common.ErrQuotaExceeded
}
- if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath,
+ if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath,
fileSize, os.O_TRUNC); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, c.GetPermissionDeniedError()
diff --git a/templates/webadmin/eventrule.html b/templates/webadmin/eventrule.html
index b951cff0..d14cc2fd 100644
--- a/templates/webadmin/eventrule.html
+++ b/templates/webadmin/eventrule.html
@@ -350,7 +350,7 @@ along with this program. If not, see