diff --git a/common/connection.go b/common/connection.go index 92a432be..bf31377a 100644 --- a/common/connection.go +++ b/common/connection.go @@ -408,6 +408,9 @@ func (c *BaseConnection) RemoveDir(virtualPath string) error { // Rename renames (moves) virtualSourcePath to virtualTargetPath func (c *BaseConnection) Rename(virtualSourcePath, virtualTargetPath string) error { + if virtualSourcePath == virtualTargetPath { + return fmt.Errorf("the rename source and target cannot be the same: %w", c.GetOpUnsupportedError()) + } fsSrc, fsSourcePath, err := c.GetFsAndResolvedPath(virtualSourcePath) if err != nil { return err diff --git a/common/protocol_test.go b/common/protocol_test.go index d19a9c9c..d78a3315 100644 --- a/common/protocol_test.go +++ b/common/protocol_test.go @@ -209,6 +209,10 @@ func TestBaseConnection(t *testing.T) { if assert.NoError(t, err) { assert.True(t, info.IsDir()) } + err = client.Rename(testDir, testDir) + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "the rename source and target cannot be the same") + } err = client.RemoveDirectory(testDir) assert.NoError(t, err) err = client.Remove(testFileName) @@ -220,6 +224,10 @@ func TestBaseConnection(t *testing.T) { err = f.Close() assert.NoError(t, err) linkName := testFileName + ".link" + err = client.Rename(testFileName, testFileName) + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "the rename source and target cannot be the same") + } err = client.Symlink(testFileName, linkName) assert.NoError(t, err) err = client.Symlink(testFileName, testFileName) diff --git a/ftpd/internal_test.go b/ftpd/internal_test.go index 5f2ca9b2..83d4172b 100644 --- a/ftpd/internal_test.go +++ b/ftpd/internal_test.go @@ -648,9 +648,7 @@ func TestResolvePathErrors(t *testing.T) { assert.EqualError(t, err, common.ErrGenericFailure.Error()) } err = connection.Rename("", "") - if assert.Error(t, err) { - assert.EqualError(t, err, common.ErrGenericFailure.Error()) - } + assert.ErrorIs(t, err, common.ErrOpUnsupported) err = connection.Symlink("", "") if assert.Error(t, err) { assert.EqualError(t, err, common.ErrGenericFailure.Error()) diff --git a/go.mod b/go.mod index 0e0a9e4f..a46b7275 100644 --- a/go.mod +++ b/go.mod @@ -140,7 +140,7 @@ require ( replace ( github.com/eikenb/pipeat => github.com/drakkan/pipeat v0.0.0-20210805162858-70e57fa8a639 - github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20211107071448-34ff70e85dfb + github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20211227100741-2a2e613fb19d github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20211216170250-0a05a5747f0f golang.org/x/net => github.com/drakkan/net v0.0.0-20211210172952-3f0f9446f73f diff --git a/go.sum b/go.sum index 68794dbd..1247b2a9 100644 --- a/go.sum +++ b/go.sum @@ -227,8 +227,8 @@ github.com/drakkan/crypto v0.0.0-20211216170250-0a05a5747f0f h1:12WWFMrTzDfKo/7s github.com/drakkan/crypto v0.0.0-20211216170250-0a05a5747f0f/go.mod h1:SiM6ypd8Xu1xldObYtbDztuUU7xUzMnUULfphXFZmro= github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA= github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU= -github.com/drakkan/ftpserverlib v0.0.0-20211107071448-34ff70e85dfb h1:cT/w4XStm7m022JgVqmrXZLcZ4UjoUER1VW5/5gd6ec= -github.com/drakkan/ftpserverlib v0.0.0-20211107071448-34ff70e85dfb/go.mod h1:fBiQ19WDhtvKArMu0Pifg71k+0xqRYn+F0d9AsjkZw8= +github.com/drakkan/ftpserverlib v0.0.0-20211227100741-2a2e613fb19d h1:md7VSy/KrGNBEy376AhmJJ7YzZ+f35Y7mZFMcM2qGmg= +github.com/drakkan/ftpserverlib v0.0.0-20211227100741-2a2e613fb19d/go.mod h1:fBiQ19WDhtvKArMu0Pifg71k+0xqRYn+F0d9AsjkZw8= github.com/drakkan/net v0.0.0-20211210172952-3f0f9446f73f h1:8XuTk84FMb6DGc5MxmPkswjO5m4XFqGoZomO/Q8CUwQ= github.com/drakkan/net v0.0.0-20211210172952-3f0f9446f73f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= github.com/drakkan/pipeat v0.0.0-20210805162858-70e57fa8a639 h1:8tfGdb4kg/YCvAbIrsMazgoNtnqdOqQVDKW12uUCuuU= diff --git a/httpd/api_utils.go b/httpd/api_utils.go index b490b407..e64eb236 100644 --- a/httpd/api_utils.go +++ b/httpd/api_utils.go @@ -101,6 +101,8 @@ func getMappedStatusCode(err error) int { statusCode = http.StatusNotFound case errors.Is(err, common.ErrQuotaExceeded): statusCode = http.StatusRequestEntityTooLarge + case errors.Is(err, common.ErrOpUnsupported): + statusCode = http.StatusBadRequest default: statusCode = http.StatusInternalServerError } diff --git a/httpd/httpd_test.go b/httpd/httpd_test.go index a4dc266b..35caa5b5 100644 --- a/httpd/httpd_test.go +++ b/httpd/httpd_test.go @@ -9219,7 +9219,7 @@ func TestShareUploadSingle(t *testing.T) { assert.NoError(t, err) req.SetBasicAuth(defaultUsername, defaultPassword) rr = executeRequest(req) - checkResponseCode(t, http.StatusInternalServerError, rr) + checkResponseCode(t, http.StatusBadRequest, rr) assert.Contains(t, rr.Body.String(), "operation unsupported") share, err = dataprovider.ShareExists(objectID, user.Username) @@ -10240,6 +10240,19 @@ func TestWebDirsAPI(t *testing.T) { if assert.Len(t, contents, 1) { assert.Equal(t, testDir, contents[0]["name"]) } + // rename a dir with the same source and target name + req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target="+testDir, nil) + assert.NoError(t, err) + setBearerForReq(req, webAPIToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusBadRequest, rr) + assert.Contains(t, rr.Body.String(), "operation unsupported") + req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target=%2F"+testDir+"%2F", nil) + assert.NoError(t, err) + setBearerForReq(req, webAPIToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusBadRequest, rr) + assert.Contains(t, rr.Body.String(), "operation unsupported") // create a dir with missing parents req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+url.QueryEscape(path.Join("/sub/dir", testDir)), nil) assert.NoError(t, err) @@ -10556,6 +10569,13 @@ func TestWebFilesAPI(t *testing.T) { setBearerForReq(req, webAPIToken) rr = executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr) + // rename a file with target name equal to source name + req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=file1.txt&target=file1.txt", nil) + assert.NoError(t, err) + setBearerForReq(req, webAPIToken) + rr = executeRequest(req) + checkResponseCode(t, http.StatusBadRequest, rr) + assert.Contains(t, rr.Body.String(), "operation unsupported") // delete a file req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file2.txt", nil) assert.NoError(t, err) @@ -10717,7 +10737,7 @@ func TestWebUploadErrors(t *testing.T) { req.Header.Add("Content-Type", writer.FormDataContentType()) setBearerForReq(req, webAPIToken) rr = executeRequest(req) - checkResponseCode(t, http.StatusInternalServerError, rr) + checkResponseCode(t, http.StatusBadRequest, rr) assert.Contains(t, rr.Body.String(), "operation unsupported") // try to upload to a missing parent directory _, err = reader.Seek(0, io.SeekStart) diff --git a/templates/webclient/files.html b/templates/webclient/files.html index 0781b65c..4807fac7 100644 --- a/templates/webclient/files.html +++ b/templates/webclient/files.html @@ -541,6 +541,8 @@ spinnerDone = true; if (!has_errors){ location.reload(); + } else { + table.button('delete:name').enable(true); } return; } @@ -786,7 +788,9 @@ $('#errorMsg').show(); setTimeout(function () { $('#errorMsg').hide(); - }, 5000); + }, 8000); + var selectedItems = table.column(0).checkboxes.selected().length; + table.button('rename:name').enable(selectedItems == 1); } }); });