mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 06:40:54 +03:00
441
ftpd/internal_test.go
Normal file
441
ftpd/internal_test.go
Normal file
@@ -0,0 +1,441 @@
|
||||
package ftpd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/drakkan/sftpgo/common"
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/vfs"
|
||||
)
|
||||
|
||||
const (
|
||||
configDir = ".."
|
||||
)
|
||||
|
||||
type mockFTPClientContext struct {
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) Path() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) SetDebug(debug bool) {}
|
||||
|
||||
func (cc mockFTPClientContext) Debug() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) ID() uint32 {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) RemoteAddr() net.Addr {
|
||||
return &net.IPAddr{IP: []byte("127.0.0.1")}
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) LocalAddr() net.Addr {
|
||||
return &net.IPAddr{IP: []byte("127.0.0.1")}
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) GetClientVersion() string {
|
||||
return "mock version"
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) Close(code int, message string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MockOsFs mockable OsFs
|
||||
type MockOsFs struct {
|
||||
vfs.Fs
|
||||
err error
|
||||
statErr error
|
||||
isAtomicUploadSupported bool
|
||||
}
|
||||
|
||||
// Name returns the name for the Fs implementation
|
||||
func (fs MockOsFs) Name() string {
|
||||
return "mockOsFs"
|
||||
}
|
||||
|
||||
// IsUploadResumeSupported returns true if upload resume is supported
|
||||
func (MockOsFs) IsUploadResumeSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAtomicUploadSupported returns true if atomic upload is supported
|
||||
func (fs MockOsFs) IsAtomicUploadSupported() bool {
|
||||
return fs.isAtomicUploadSupported
|
||||
}
|
||||
|
||||
// Stat returns a FileInfo describing the named file
|
||||
func (fs MockOsFs) Stat(name string) (os.FileInfo, error) {
|
||||
if fs.statErr != nil {
|
||||
return nil, fs.statErr
|
||||
}
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
// Lstat returns a FileInfo describing the named file
|
||||
func (fs MockOsFs) Lstat(name string) (os.FileInfo, error) {
|
||||
if fs.statErr != nil {
|
||||
return nil, fs.statErr
|
||||
}
|
||||
return os.Lstat(name)
|
||||
}
|
||||
|
||||
// Remove removes the named file or (empty) directory.
|
||||
func (fs MockOsFs) Remove(name string, isDir bool) error {
|
||||
if fs.err != nil {
|
||||
return fs.err
|
||||
}
|
||||
return os.Remove(name)
|
||||
}
|
||||
|
||||
// Rename renames (moves) source to target
|
||||
func (fs MockOsFs) Rename(source, target string) error {
|
||||
if fs.err != nil {
|
||||
return fs.err
|
||||
}
|
||||
return os.Rename(source, target)
|
||||
}
|
||||
|
||||
func newMockOsFs(err, statErr error, atomicUpload bool, connectionID, rootDir string) vfs.Fs {
|
||||
return &MockOsFs{
|
||||
Fs: vfs.NewOsFs(connectionID, rootDir, nil),
|
||||
err: err,
|
||||
statErr: statErr,
|
||||
isAtomicUploadSupported: atomicUpload,
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitialization(t *testing.T) {
|
||||
c := &Configuration{
|
||||
BindPort: 2121,
|
||||
CertificateFile: "acert",
|
||||
CertificateKeyFile: "akey",
|
||||
}
|
||||
err := c.Initialize(configDir)
|
||||
assert.Error(t, err)
|
||||
c.CertificateFile = ""
|
||||
c.CertificateKeyFile = ""
|
||||
c.BannerFile = "afile"
|
||||
server, err := NewServer(c, configDir)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, "", server.initialMsg)
|
||||
_, err = server.GetTLSConfig()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
err = ReloadTLSCertificate()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServerGetSettings(t *testing.T) {
|
||||
oldConfig := common.Config
|
||||
c := &Configuration{
|
||||
BindPort: 2121,
|
||||
PassivePortRange: PortRange{
|
||||
Start: 10000,
|
||||
End: 11000,
|
||||
},
|
||||
}
|
||||
server, err := NewServer(c, configDir)
|
||||
assert.NoError(t, err)
|
||||
settings, err := server.GetSettings()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 10000, settings.PassiveTransferPortRange.Start)
|
||||
assert.Equal(t, 11000, settings.PassiveTransferPortRange.End)
|
||||
|
||||
common.Config.ProxyProtocol = 1
|
||||
common.Config.ProxyAllowed = []string{"invalid"}
|
||||
_, err = server.GetSettings()
|
||||
assert.Error(t, err)
|
||||
server.config.BindPort = 8021
|
||||
_, err = server.GetSettings()
|
||||
assert.Error(t, err)
|
||||
|
||||
common.Config = oldConfig
|
||||
}
|
||||
|
||||
func TestUserInvalidParams(t *testing.T) {
|
||||
u := dataprovider.User{
|
||||
HomeDir: "invalid",
|
||||
}
|
||||
c := &Configuration{
|
||||
BindPort: 2121,
|
||||
PassivePortRange: PortRange{
|
||||
Start: 10000,
|
||||
End: 11000,
|
||||
},
|
||||
}
|
||||
server, err := NewServer(c, configDir)
|
||||
assert.NoError(t, err)
|
||||
_, err = server.validateUser(u, mockFTPClientContext{})
|
||||
assert.Error(t, err)
|
||||
|
||||
u.Username = "a"
|
||||
u.HomeDir = filepath.Clean(os.TempDir())
|
||||
subDir := "subdir"
|
||||
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
|
||||
vdirPath1 := "/vdir1"
|
||||
mappedPath2 := filepath.Join(os.TempDir(), "vdir1", subDir)
|
||||
vdirPath2 := "/vdir2"
|
||||
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
||||
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
||||
MappedPath: mappedPath1,
|
||||
},
|
||||
VirtualPath: vdirPath1,
|
||||
})
|
||||
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
||||
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
||||
MappedPath: mappedPath2,
|
||||
},
|
||||
VirtualPath: vdirPath2,
|
||||
})
|
||||
_, err = server.validateUser(u, mockFTPClientContext{})
|
||||
assert.Error(t, err)
|
||||
u.VirtualFolders = nil
|
||||
_, err = server.validateUser(u, mockFTPClientContext{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestClientVersion(t *testing.T) {
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
user := dataprovider.User{}
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, nil),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
common.Connections.Add(connection)
|
||||
stats := common.Connections.GetStats()
|
||||
if assert.Len(t, stats, 1) {
|
||||
assert.Equal(t, "mock version", stats[0].ClientVersion)
|
||||
common.Connections.Remove(connection.GetID())
|
||||
}
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
}
|
||||
|
||||
func TestDriverMethodsNotImplemented(t *testing.T) {
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
user := dataprovider.User{}
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, nil),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
_, err := connection.Create("")
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
err = connection.MkdirAll("", os.ModePerm)
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
_, err = connection.Open("")
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
_, err = connection.OpenFile("", 0, os.ModePerm)
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
err = connection.RemoveAll("")
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
assert.Equal(t, connection.GetID(), connection.Name())
|
||||
}
|
||||
|
||||
func TestResolvePathErrors(t *testing.T) {
|
||||
user := dataprovider.User{
|
||||
HomeDir: "invalid",
|
||||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
fs := vfs.NewOsFs(connID, user.HomeDir, nil)
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
err := connection.Mkdir("", os.ModePerm)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
err = connection.Remove("")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
err = connection.Rename("", "")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
_, err = connection.Stat("")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
err = connection.Chmod("", os.ModePerm)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
err = connection.Chtimes("", time.Now(), time.Now())
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
_, err = connection.ReadDir("")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
_, err = connection.GetHandle("", 0)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadFileStatError(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("this test is not available on Windows")
|
||||
}
|
||||
user := dataprovider.User{
|
||||
Username: "user",
|
||||
HomeDir: filepath.Clean(os.TempDir()),
|
||||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
fs := vfs.NewOsFs(connID, user.HomeDir, nil)
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
testFile := filepath.Join(user.HomeDir, "test", "testfile")
|
||||
err := os.MkdirAll(filepath.Dir(testFile), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(testFile, []byte("data"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = os.Chmod(filepath.Dir(testFile), 0001)
|
||||
assert.NoError(t, err)
|
||||
_, err = connection.uploadFile(testFile, "test", 0)
|
||||
assert.Error(t, err)
|
||||
err = os.Chmod(filepath.Dir(testFile), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(filepath.Dir(testFile))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUploadOverwriteErrors(t *testing.T) {
|
||||
user := dataprovider.User{
|
||||
Username: "user",
|
||||
HomeDir: filepath.Clean(os.TempDir()),
|
||||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir())
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
flags := 0
|
||||
flags |= os.O_APPEND
|
||||
_, err := connection.handleFTPUploadToExistingFile(flags, "", "", 0, "")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrOpUnsupported.Error())
|
||||
}
|
||||
|
||||
f, err := ioutil.TempFile("", "temp")
|
||||
assert.NoError(t, err)
|
||||
err = f.Close()
|
||||
assert.NoError(t, err)
|
||||
flags = 0
|
||||
flags |= os.O_CREATE
|
||||
flags |= os.O_TRUNC
|
||||
tr, err := connection.handleFTPUploadToExistingFile(flags, f.Name(), f.Name(), 123, f.Name())
|
||||
if assert.NoError(t, err) {
|
||||
transfer := tr.(*transfer)
|
||||
transfers := connection.GetTransfers()
|
||||
if assert.Equal(t, 1, len(transfers)) {
|
||||
assert.Equal(t, transfers[0].ID, transfer.GetID())
|
||||
assert.Equal(t, int64(123), transfer.InitialSize)
|
||||
err = transfer.Close()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(connection.GetTransfers()))
|
||||
}
|
||||
}
|
||||
err = os.Remove(f.Name())
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = connection.handleFTPUploadToExistingFile(0, filepath.Join(os.TempDir(), "sub", "file"),
|
||||
filepath.Join(os.TempDir(), "sub", "file1"), 0, "/sub/file1")
|
||||
assert.Error(t, err)
|
||||
connection.Fs = vfs.NewOsFs(connID, user.GetHomeDir(), nil)
|
||||
_, err = connection.handleFTPUploadToExistingFile(0, "missing1", "missing2", 0, "missing")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestTransferErrors(t *testing.T) {
|
||||
testfile := "testfile"
|
||||
file, err := os.Create(testfile)
|
||||
assert.NoError(t, err)
|
||||
user := dataprovider.User{
|
||||
Username: "user",
|
||||
HomeDir: filepath.Clean(os.TempDir()),
|
||||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir())
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
baseTransfer := common.NewBaseTransfer(file, connection.BaseConnection, nil, file.Name(), testfile, common.TransferDownload,
|
||||
0, 0, false)
|
||||
tr := newTransfer(baseTransfer, nil, nil, 0)
|
||||
err = tr.Close()
|
||||
assert.NoError(t, err)
|
||||
buf := make([]byte, 64)
|
||||
_, err = tr.Read(buf)
|
||||
assert.Error(t, err)
|
||||
err = tr.Close()
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrTransferClosed.Error())
|
||||
}
|
||||
assert.Len(t, connection.GetTransfers(), 0)
|
||||
|
||||
r, _, err := pipeat.Pipe()
|
||||
assert.NoError(t, err)
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testfile, testfile,
|
||||
common.TransferUpload, 0, 0, false)
|
||||
tr = newTransfer(baseTransfer, nil, r, 0)
|
||||
err = tr.closeIO()
|
||||
assert.NoError(t, err)
|
||||
|
||||
r, w, err := pipeat.Pipe()
|
||||
assert.NoError(t, err)
|
||||
pipeWriter := vfs.NewPipeWriter(w)
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testfile, testfile,
|
||||
common.TransferUpload, 0, 0, false)
|
||||
tr = newTransfer(baseTransfer, pipeWriter, nil, 0)
|
||||
_, err = tr.Seek(1, 0)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrOpUnsupported.Error())
|
||||
}
|
||||
|
||||
err = r.Close()
|
||||
assert.NoError(t, err)
|
||||
errFake := fmt.Errorf("fake upload error")
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
pipeWriter.Done(errFake)
|
||||
}()
|
||||
err = tr.closeIO()
|
||||
assert.EqualError(t, err, errFake.Error())
|
||||
err = os.Remove(testfile)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user