mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 09:00:27 +00:00
FTP: add support for AVBL command
This commit is contained in:
parent
1dce1eff48
commit
2a95d031ea
12 changed files with 153 additions and 6 deletions
|
@ -87,8 +87,8 @@ var (
|
|||
ErrGenericFailure = errors.New("failure")
|
||||
ErrQuotaExceeded = errors.New("denying write due to space limit")
|
||||
ErrSkipPermissionsCheck = errors.New("permission check skipped")
|
||||
ErrConnectionDenied = errors.New("You are not allowed to connect")
|
||||
ErrNoBinding = errors.New("No binding configured")
|
||||
ErrConnectionDenied = errors.New("you are not allowed to connect")
|
||||
ErrNoBinding = errors.New("no binding configured")
|
||||
errNoTransfer = errors.New("requested transfer not found")
|
||||
errTransferMismatch = errors.New("transfer mismatch")
|
||||
)
|
||||
|
|
|
@ -1395,7 +1395,7 @@ func TestUploadOverwriteVfolder(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAllocate(t *testing.T) {
|
||||
func TestAllocateAvailable(t *testing.T) {
|
||||
u := getTestUser()
|
||||
mappedPath := filepath.Join(os.TempDir(), "vdir")
|
||||
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
||||
|
@ -1415,6 +1415,16 @@ func TestAllocate(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusCommandOK, code)
|
||||
assert.Equal(t, "Done !", response)
|
||||
|
||||
code, response, err = client.SendCustomCommand("AVBL /vdir")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
assert.Equal(t, "110", response)
|
||||
|
||||
code, _, err = client.SendCustomCommand("AVBL")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -1442,6 +1452,12 @@ func TestAllocate(t *testing.T) {
|
|||
|
||||
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
code, response, err = client.SendCustomCommand("AVBL")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
assert.Equal(t, "1", response)
|
||||
|
||||
// we still have space in vdir
|
||||
code, response, err = client.SendCustomCommand("allo 50")
|
||||
assert.NoError(t, err)
|
||||
|
@ -1475,10 +1491,38 @@ func TestAllocate(t *testing.T) {
|
|||
assert.Equal(t, ftp.StatusFileUnavailable, code)
|
||||
assert.Contains(t, response, common.ErrQuotaExceeded.Error())
|
||||
|
||||
code, response, err = client.SendCustomCommand("AVBL")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
assert.Equal(t, "100", response)
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
user.QuotaSize = 50
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
client, err = getFTPClient(user, false)
|
||||
if assert.NoError(t, err) {
|
||||
code, response, err := client.SendCustomCommand("AVBL")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
assert.Equal(t, "0", response)
|
||||
}
|
||||
|
||||
user.QuotaSize = 1000
|
||||
user.Filters.MaxUploadFileSize = 1
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
client, err = getFTPClient(user, false)
|
||||
if assert.NoError(t, err) {
|
||||
code, response, err := client.SendCustomCommand("AVBL")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
assert.Equal(t, "1", response)
|
||||
}
|
||||
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK)
|
||||
|
@ -1489,6 +1533,30 @@ func TestAllocate(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAvailableUnsupportedFs(t *testing.T) {
|
||||
u := getTestUser()
|
||||
localUser, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
sftpUser, _, err := httpd.AddUser(getTestSFTPUser(), http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
client, err := getFTPClient(sftpUser, false)
|
||||
if assert.NoError(t, err) {
|
||||
code, response, err := client.SendCustomCommand("AVBL")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFileUnavailable, code)
|
||||
assert.Contains(t, response, "unable to get available size for this storage backend")
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
_, err = httpd.RemoveUser(sftpUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(localUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(localUser.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestChtimes(t *testing.T) {
|
||||
u := getTestUser()
|
||||
localUser, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
|
|
|
@ -49,7 +49,7 @@ func (c *Connection) Disconnect() error {
|
|||
return c.clientContext.Close()
|
||||
}
|
||||
|
||||
// GetCommand returns an empty string
|
||||
// GetCommand returns the last received FTP command
|
||||
func (c *Connection) GetCommand() string {
|
||||
return c.clientContext.GetLastCommand()
|
||||
}
|
||||
|
@ -209,7 +209,42 @@ func (c *Connection) Chtimes(name string, atime time.Time, mtime time.Time) erro
|
|||
return c.SetStat(p, name, &attrs)
|
||||
}
|
||||
|
||||
// AllocateSpace implements ClientDriverExtensionAllocate
|
||||
// GetAvailableSpace implements ClientDriverExtensionAvailableSpace interface
|
||||
func (c *Connection) GetAvailableSpace(dirName string) (int64, error) {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
quotaResult := c.HasSpace(false, path.Join(dirName, "fakefile.txt"))
|
||||
|
||||
if !quotaResult.HasSpace {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if quotaResult.AllowedSize == 0 {
|
||||
// no quota restrictions
|
||||
if c.User.Filters.MaxUploadFileSize > 0 {
|
||||
return c.User.Filters.MaxUploadFileSize, nil
|
||||
}
|
||||
|
||||
p, err := c.Fs.ResolvePath(dirName)
|
||||
if err != nil {
|
||||
return 0, c.GetFsError(err)
|
||||
}
|
||||
|
||||
return c.Fs.GetAvailableDiskSize(p)
|
||||
}
|
||||
|
||||
// the available space is the minimum between MaxUploadFileSize, if setted,
|
||||
// and quota allowed size
|
||||
if c.User.Filters.MaxUploadFileSize > 0 {
|
||||
if c.User.Filters.MaxUploadFileSize < quotaResult.AllowedSize {
|
||||
return c.User.Filters.MaxUploadFileSize, nil
|
||||
}
|
||||
}
|
||||
|
||||
return quotaResult.AllowedSize, nil
|
||||
}
|
||||
|
||||
// AllocateSpace implements ClientDriverExtensionAllocate interface
|
||||
func (c *Connection) AllocateSpace(size int) error {
|
||||
c.UpdateLastActivity()
|
||||
// check the max allowed file size first
|
||||
|
|
|
@ -342,6 +342,10 @@ func TestResolvePathErrors(t *testing.T) {
|
|||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
_, err = connection.GetAvailableSpace("")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadFileStatError(t *testing.T) {
|
||||
|
|
1
go.mod
1
go.mod
|
@ -34,6 +34,7 @@ require (
|
|||
github.com/rs/xid v1.2.1
|
||||
github.com/rs/zerolog v1.20.0
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.20.11
|
||||
github.com/spf13/afero v1.5.1
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.1.1
|
||||
|
|
5
go.sum
5
go.sum
|
@ -85,6 +85,7 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go
|
|||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
|
@ -208,6 +209,7 @@ github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp
|
|||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
|
@ -559,6 +561,8 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH
|
|||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=
|
||||
github.com/shirou/gopsutil/v3 v3.20.11 h1:NeVf1K0cgxsWz+N3671ojRptdgzvp7BXL3KV21R0JnA=
|
||||
github.com/shirou/gopsutil/v3 v3.20.11/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
|
@ -742,6 +746,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
@ -701,6 +701,11 @@ func (*AzureBlobFs) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*AzureBlobFs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
return 0, errStorageSizeUnavailable
|
||||
}
|
||||
|
||||
func (fs *AzureBlobFs) isEqual(key string, virtualName string) bool {
|
||||
if key == virtualName {
|
||||
return true
|
||||
|
|
|
@ -754,3 +754,8 @@ func (fs *GCSFs) GetMimeType(name string) (string, error) {
|
|||
func (fs *GCSFs) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*GCSFs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
return 0, errStorageSizeUnavailable
|
||||
}
|
||||
|
|
10
vfs/osfs.go
10
vfs/osfs.go
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/rs/xid"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/utils"
|
||||
|
@ -477,3 +478,12 @@ func (fs *OsFs) GetMimeType(name string) (string, error) {
|
|||
func (*OsFs) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*OsFs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
usage, err := disk.Usage(dirName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(usage.Free), nil
|
||||
}
|
||||
|
|
|
@ -703,3 +703,8 @@ func (fs *S3Fs) GetMimeType(name string) (string, error) {
|
|||
func (*S3Fs) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*S3Fs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
return 0, errStorageSizeUnavailable
|
||||
}
|
||||
|
|
|
@ -494,6 +494,11 @@ func (fs *SFTPFs) Close() error {
|
|||
return sshErr
|
||||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*SFTPFs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
return 0, errStorageSizeUnavailable
|
||||
}
|
||||
|
||||
func (fs *SFTPFs) checkConnection() error {
|
||||
err := fs.closed()
|
||||
if err == nil {
|
||||
|
|
|
@ -22,7 +22,10 @@ import (
|
|||
|
||||
const dirMimeType = "inode/directory"
|
||||
|
||||
var validAzAccessTier = []string{"", "Archive", "Hot", "Cool"}
|
||||
var (
|
||||
validAzAccessTier = []string{"", "Archive", "Hot", "Cool"}
|
||||
errStorageSizeUnavailable = errors.New("unable to get available size for this storage backend")
|
||||
)
|
||||
|
||||
// Fs defines the interface for filesystem backends
|
||||
type Fs interface {
|
||||
|
@ -57,6 +60,7 @@ type Fs interface {
|
|||
Join(elem ...string) string
|
||||
HasVirtualFolders() bool
|
||||
GetMimeType(name string) (string, error)
|
||||
GetAvailableDiskSize(dirName string) (int64, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue