// Copyright (C) 2019-2023 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package common_test
import (
	"bufio"
	"bytes"
	"crypto/rand"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"math"
	"net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"testing"
	"time"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/jackc/pgx/v5/stdlib"
	_ "github.com/mattn/go-sqlite3"
	"github.com/mhale/smtpd"
	"github.com/minio/sio"
	"github.com/pkg/sftp"
	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"
	"github.com/rs/xid"
	"github.com/rs/zerolog"
	"github.com/sftpgo/sdk"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/studio-b12/gowebdav"
	"golang.org/x/crypto/ssh"
	"github.com/drakkan/sftpgo/v2/internal/common"
	"github.com/drakkan/sftpgo/v2/internal/config"
	"github.com/drakkan/sftpgo/v2/internal/dataprovider"
	"github.com/drakkan/sftpgo/v2/internal/httpclient"
	"github.com/drakkan/sftpgo/v2/internal/httpdtest"
	"github.com/drakkan/sftpgo/v2/internal/kms"
	"github.com/drakkan/sftpgo/v2/internal/logger"
	"github.com/drakkan/sftpgo/v2/internal/mfa"
	"github.com/drakkan/sftpgo/v2/internal/sftpd"
	"github.com/drakkan/sftpgo/v2/internal/smtp"
	"github.com/drakkan/sftpgo/v2/internal/util"
	"github.com/drakkan/sftpgo/v2/internal/vfs"
	"github.com/drakkan/sftpgo/v2/internal/webdavd"
)
const (
	httpAddr              = "127.0.0.1:9999"
	httpProxyAddr         = "127.0.0.1:7777"
	sftpServerAddr        = "127.0.0.1:4022"
	smtpServerAddr        = "127.0.0.1:2525"
	webDavServerPort      = 9191
	httpFsPort            = 34567
	defaultUsername       = "test_common_sftp"
	defaultPassword       = "test_password"
	defaultSFTPUsername   = "test_common_sftpfs_user"
	defaultHTTPFsUsername = "httpfs_ftp_user"
	httpFsWellKnowDir     = "/wellknow"
	osWindows             = "windows"
	testFileName          = "test_file_common_sftp.dat"
	testDir               = "test_dir_common"
)
var (
	configDir         = filepath.Join(".", "..", "..")
	allPerms          = []string{dataprovider.PermAny}
	homeBasePath      string
	logFilePath       string
	backupsPath       string
	testFileContent   = []byte("test data")
	lastReceivedEmail receivedEmail
)
func TestMain(m *testing.M) {
	homeBasePath = os.TempDir()
	logFilePath = filepath.Join(configDir, "common_test.log")
	backupsPath = filepath.Join(os.TempDir(), "backups")
	logger.InitLogger(logFilePath, 5, 1, 28, false, false, zerolog.DebugLevel)
	os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "1")
	os.Setenv("SFTPGO_COMMON__ALLOW_SELF_CONNECTIONS", "1")
	os.Setenv("SFTPGO_DEFAULT_ADMIN_USERNAME", "admin")
	os.Setenv("SFTPGO_DEFAULT_ADMIN_PASSWORD", "password")
	err := config.LoadConfig(configDir, "")
	if err != nil {
		logger.ErrorToConsole("error loading configuration: %v", err)
		os.Exit(1)
	}
	providerConf := config.GetProviderConf()
	providerConf.BackupsPath = backupsPath
	logger.InfoToConsole("Starting COMMON tests, provider: %v", providerConf.Driver)
	err = dataprovider.Initialize(providerConf, configDir, true)
	if err != nil {
		logger.ErrorToConsole("error initializing data provider: %v", err)
		os.Exit(1)
	}
	err = common.Initialize(config.GetCommonConfig(), 0)
	if err != nil {
		logger.WarnToConsole("error initializing common: %v", err)
		os.Exit(1)
	}
	httpConfig := config.GetHTTPConfig()
	httpConfig.Timeout = 5
	httpConfig.RetryMax = 0
	httpConfig.Initialize(configDir) //nolint:errcheck
	kmsConfig := config.GetKMSConfig()
	err = kmsConfig.Initialize()
	if err != nil {
		logger.ErrorToConsole("error initializing kms: %v", err)
		os.Exit(1)
	}
	mfaConfig := config.GetMFAConfig()
	err = mfaConfig.Initialize()
	if err != nil {
		logger.ErrorToConsole("error initializing MFA: %v", err)
		os.Exit(1)
	}
	sftpdConf := config.GetSFTPDConfig()
	sftpdConf.Bindings[0].Port = 4022
	sftpdConf.EnabledSSHCommands = []string{"*"}
	sftpdConf.Bindings = append(sftpdConf.Bindings, sftpd.Binding{
		Port: 4024,
	})
	sftpdConf.KeyboardInteractiveAuthentication = true
	httpdConf := config.GetHTTPDConfig()
	httpdConf.Bindings[0].Port = 4080
	httpdtest.SetBaseURL("http://127.0.0.1:4080")
	webDavConf := config.GetWebDAVDConfig()
	webDavConf.Bindings = []webdavd.Binding{
		{
			Port: webDavServerPort,
		},
	}
	go func() {
		if err := sftpdConf.Initialize(configDir); err != nil {
			logger.ErrorToConsole("could not start SFTP server: %v", err)
			os.Exit(1)
		}
	}()
	go func() {
		if err := httpdConf.Initialize(configDir, 0); err != nil {
			logger.ErrorToConsole("could not start HTTP server: %v", err)
			os.Exit(1)
		}
	}()
	go func() {
		if err := webDavConf.Initialize(configDir); err != nil {
			logger.ErrorToConsole("could not start WebDAV server: %v", err)
			os.Exit(1)
		}
	}()
	waitTCPListening(sftpdConf.Bindings[0].GetAddress())
	waitTCPListening(httpdConf.Bindings[0].GetAddress())
	waitTCPListening(webDavConf.Bindings[0].GetAddress())
	startHTTPFs()
	go func() {
		// start a test HTTP server to receive action notifications
		http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintf(w, "OK\n")
		})
		http.HandleFunc("/404", func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusNotFound)
			fmt.Fprintf(w, "Not found\n")
		})
		http.HandleFunc("/multipart", func(w http.ResponseWriter, r *http.Request) {
			err := r.ParseMultipartForm(1048576)
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				fmt.Fprintf(w, "KO\n")
				return
			}
			defer r.MultipartForm.RemoveAll() //nolint:errcheck
			fmt.Fprintf(w, "OK\n")
		})
		if err := http.ListenAndServe(httpAddr, nil); err != nil {
			logger.ErrorToConsole("could not start HTTP notification server: %v", err)
			os.Exit(1)
		}
	}()
	go func() {
		common.Config.ProxyProtocol = 2
		listener, err := net.Listen("tcp", httpProxyAddr)
		if err != nil {
			logger.ErrorToConsole("error creating listener for proxy protocol server: %v", err)
			os.Exit(1)
		}
		proxyListener, err := common.Config.GetProxyListener(listener)
		if err != nil {
			logger.ErrorToConsole("error creating proxy protocol listener: %v", err)
			os.Exit(1)
		}
		common.Config.ProxyProtocol = 0
		s := &http.Server{}
		if err := s.Serve(proxyListener); err != nil {
			logger.ErrorToConsole("could not start HTTP proxy protocol server: %v", err)
			os.Exit(1)
		}
	}()
	go func() {
		if err := smtpd.ListenAndServe(smtpServerAddr, func(remoteAddr net.Addr, from string, to []string, data []byte) error {
			lastReceivedEmail.set(from, to, data)
			return nil
		}, "SFTPGo test", "localhost"); err != nil {
			logger.ErrorToConsole("could not start SMTP server: %v", err)
			os.Exit(1)
		}
	}()
	waitTCPListening(httpAddr)
	waitTCPListening(httpProxyAddr)
	waitTCPListening(smtpServerAddr)
	exitCode := m.Run()
	os.Remove(logFilePath)
	os.RemoveAll(backupsPath)
	os.Exit(exitCode)
}
func TestBaseConnection(t *testing.T) {
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		assert.NoError(t, checkBasicSFTP(client))
		_, err = client.ReadDir(testDir)
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = client.RemoveDirectory(testDir)
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = client.Mkdir(testDir)
		assert.Error(t, err)
		info, err := client.Stat(testDir)
		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.Rename(testDir, path.Join(testDir, "sub"))
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.RemoveDirectory(testDir)
		assert.NoError(t, err)
		err = client.Remove(testFileName)
		assert.ErrorIs(t, err, os.ErrNotExist)
		f, err := client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		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)
		assert.Error(t, err)
		info, err = client.Stat(testFileName)
		if assert.NoError(t, err) {
			assert.Equal(t, int64(len(testFileContent)), info.Size())
			assert.False(t, info.IsDir())
		}
		info, err = client.Lstat(linkName)
		if assert.NoError(t, err) {
			assert.NotEqual(t, int64(7), info.Size())
			assert.True(t, info.Mode()&os.ModeSymlink != 0)
			assert.False(t, info.IsDir())
		}
		err = client.RemoveDirectory(linkName)
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
		}
		err = client.Remove(testFileName)
		assert.NoError(t, err)
		err = client.Remove(linkName)
		assert.NoError(t, err)
		err = client.Rename(testFileName, "test")
		assert.ErrorIs(t, err, os.ErrNotExist)
		f, err = client.Create(testFileName)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		err = client.Rename(testFileName, testFileName+"1")
		assert.NoError(t, err)
		err = client.Remove(testFileName + "1")
		assert.NoError(t, err)
		err = client.RemoveDirectory("missing")
		assert.Error(t, err)
	} else {
		printLatestLogs(10)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestRemoveAll(t *testing.T) {
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	webDavClient := getWebDavClient(user)
	err = webDavClient.RemoveAll("/")
	if assert.Error(t, err) {
		assert.True(t, gowebdav.IsErrCode(err, http.StatusForbidden))
	}
	testDir := "baseDir"
	err = webDavClient.RemoveAll(testDir)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, testFileName), 1234, client)
		assert.NoError(t, err)
		err = webDavClient.RemoveAll(path.Join(testDir, testFileName))
		assert.NoError(t, err)
		_, err = client.Stat(path.Join(testDir, testFileName))
		assert.Error(t, err)
		err = writeSFTPFile(path.Join(testDir, testFileName), 1234, client)
		assert.NoError(t, err)
		err = webDavClient.RemoveAll(testDir)
		assert.NoError(t, err)
		_, err = client.Stat(testDir)
		assert.Error(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestRelativeSymlinks(t *testing.T) {
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		linkName := testFileName + "_link"
		err = client.Symlink("non-existent-file", linkName)
		assert.NoError(t, err)
		err = client.Remove(linkName)
		assert.NoError(t, err)
		testDir := "sub"
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		f, err := client.Create(path.Join(testDir, testFileName))
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		err = client.Symlink(path.Join(testDir, testFileName), linkName)
		assert.NoError(t, err)
		_, err = client.Stat(linkName)
		assert.NoError(t, err)
		p, err := client.ReadLink(linkName)
		assert.NoError(t, err)
		assert.Equal(t, path.Join("/", testDir, testFileName), p)
		err = client.Remove(linkName)
		assert.NoError(t, err)
		err = client.Symlink(testFileName, path.Join(testDir, linkName))
		assert.NoError(t, err)
		_, err = client.Stat(path.Join(testDir, linkName))
		assert.NoError(t, err)
		p, err = client.ReadLink(path.Join(testDir, linkName))
		assert.NoError(t, err)
		assert.Equal(t, path.Join("/", testDir, testFileName), p)
		f, err = client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		err = client.Symlink(testFileName, linkName)
		assert.NoError(t, err)
		_, err = client.Stat(linkName)
		assert.NoError(t, err)
		p, err = client.ReadLink(linkName)
		assert.NoError(t, err)
		assert.Equal(t, path.Join("/", testFileName), p)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestCheckFsAfterUpdate(t *testing.T) {
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
	}
	// remove the home dir, it will not be re-created
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.Error(t, err)
	} else {
		printLatestLogs(10)
	}
	// update the user and login again, this time the home dir will be created
	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestSetStat(t *testing.T) {
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		f, err := client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		acmodTime := time.Now().Add(36 * time.Hour)
		err = client.Chtimes(testFileName, acmodTime, acmodTime)
		assert.NoError(t, err)
		newFi, err := client.Lstat(testFileName)
		assert.NoError(t, err)
		diff := math.Abs(newFi.ModTime().Sub(acmodTime).Seconds())
		assert.LessOrEqual(t, diff, float64(1))
		if runtime.GOOS != osWindows {
			err = client.Chown(testFileName, os.Getuid(), os.Getgid())
			assert.NoError(t, err)
		}
		newPerm := os.FileMode(0666)
		err = client.Chmod(testFileName, newPerm)
		assert.NoError(t, err)
		newFi, err = client.Lstat(testFileName)
		if assert.NoError(t, err) {
			assert.Equal(t, newPerm, newFi.Mode().Perm())
		}
		err = client.Truncate(testFileName, 2)
		assert.NoError(t, err)
		info, err := client.Stat(testFileName)
		if assert.NoError(t, err) {
			assert.Equal(t, int64(2), info.Size())
		}
		err = client.Remove(testFileName)
		assert.NoError(t, err)
		err = client.Truncate(testFileName, 0)
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = client.Chtimes(testFileName, acmodTime, acmodTime)
		assert.ErrorIs(t, err, os.ErrNotExist)
		if runtime.GOOS != osWindows {
			err = client.Chown(testFileName, os.Getuid(), os.Getgid())
			assert.ErrorIs(t, err, os.ErrNotExist)
		}
		err = client.Chmod(testFileName, newPerm)
		assert.ErrorIs(t, err, os.ErrNotExist)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestCryptFsUserUploadErrorOverwrite(t *testing.T) {
	u := getCryptFsUser()
	u.QuotaSize = 6000
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	var buf []byte
	for i := 0; i < 4000; i++ {
		buf = append(buf, []byte("a")...)
	}
	bufSize := int64(len(buf))
	reader := bytes.NewReader(buf)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		f, err := client.Create(testFileName + "_big")
		assert.NoError(t, err)
		n, err := io.Copy(f, reader)
		assert.NoError(t, err)
		assert.Equal(t, bufSize, n)
		err = f.Close()
		assert.NoError(t, err)
		encryptedSize, err := getEncryptedFileSize(bufSize)
		assert.NoError(t, err)
		expectedSize := encryptedSize
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, expectedSize, user.UsedQuotaSize)
		// now write a small file
		f, err = client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		encryptedSize, err = getEncryptedFileSize(int64(len(testFileContent)))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, expectedSize+encryptedSize, user.UsedQuotaSize)
		// try to overwrite this file with a big one, this cause an overquota error
		// the partial file is deleted and the quota updated
		_, err = reader.Seek(0, io.SeekStart)
		assert.NoError(t, err)
		f, err = client.Create(testFileName)
		assert.NoError(t, err)
		_, err = io.Copy(f, reader)
		assert.Error(t, err)
		err = f.Close()
		assert.Error(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, expectedSize, user.UsedQuotaSize)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestChtimesOpenHandle(t *testing.T) {
	localUser, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
	assert.NoError(t, err)
	u := getCryptFsUser()
	cryptFsUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	for _, user := range []dataprovider.User{localUser, sftpUser, cryptFsUser} {
		conn, client, err := getSftpClient(user)
		if assert.NoError(t, err) {
			defer conn.Close()
			defer client.Close()
			f, err := client.Create(testFileName)
			assert.NoError(t, err, "user %v", user.Username)
			f1, err := client.Create(testFileName + "1")
			assert.NoError(t, err, "user %v", user.Username)
			acmodTime := time.Now().Add(36 * time.Hour)
			err = client.Chtimes(testFileName, acmodTime, acmodTime)
			assert.NoError(t, err, "user %v", user.Username)
			_, err = f.Write(testFileContent)
			assert.NoError(t, err, "user %v", user.Username)
			err = f.Close()
			assert.NoError(t, err, "user %v", user.Username)
			err = f1.Close()
			assert.NoError(t, err, "user %v", user.Username)
			info, err := client.Lstat(testFileName)
			assert.NoError(t, err, "user %v", user.Username)
			diff := math.Abs(info.ModTime().Sub(acmodTime).Seconds())
			assert.LessOrEqual(t, diff, float64(1), "user %v", user.Username)
			info1, err := client.Lstat(testFileName + "1")
			assert.NoError(t, err, "user %v", user.Username)
			diff = math.Abs(info1.ModTime().Sub(acmodTime).Seconds())
			assert.Greater(t, diff, float64(86400), "user %v", user.Username)
		}
	}
	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(localUser.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(cryptFsUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(cryptFsUser.GetHomeDir())
	assert.NoError(t, err)
}
func TestWaitForConnections(t *testing.T) {
	u := getTestUser()
	u.UploadBandwidth = 128
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	testFileSize := int64(524288)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = common.CheckClosing()
		assert.NoError(t, err)
		var wg sync.WaitGroup
		wg.Add(1)
		go func() {
			defer wg.Done()
			time.Sleep(1 * time.Second)
			common.WaitForTransfers(10)
			common.WaitForTransfers(0)
			common.WaitForTransfers(10)
		}()
		err = writeSFTPFileNoCheck(testFileName, testFileSize, client)
		assert.NoError(t, err)
		wg.Wait()
		err = common.CheckClosing()
		assert.EqualError(t, err, common.ErrShuttingDown.Error())
		_, err = client.Stat(testFileName)
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), common.ErrShuttingDown.Error())
		}
	}
	_, _, err = getSftpClient(user)
	assert.Error(t, err)
	err = common.Initialize(common.Config, 0)
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		info, err := client.Stat(testFileName)
		if assert.NoError(t, err) {
			assert.Equal(t, testFileSize, info.Size())
		}
		err = client.Remove(testFileName)
		assert.NoError(t, err)
		var wg sync.WaitGroup
		wg.Add(1)
		go func() {
			defer wg.Done()
			time.Sleep(1 * time.Second)
			common.WaitForTransfers(1)
		}()
		err = writeSFTPFileNoCheck(testFileName, testFileSize, client)
		// we don't have an error here because the service won't really stop
		assert.NoError(t, err)
		wg.Wait()
	}
	err = common.Initialize(common.Config, 0)
	assert.NoError(t, err)
	common.WaitForTransfers(1)
	err = common.Initialize(common.Config, 0)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestCheckParentDirs(t *testing.T) {
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	testDir := "/path/to/sub/dir"
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		_, err = client.Stat(testDir)
		assert.ErrorIs(t, err, os.ErrNotExist)
		c := common.NewBaseConnection(xid.New().String(), common.ProtocolSFTP, "", "", user)
		err = c.CheckParentDirs(testDir)
		assert.NoError(t, err)
		_, err = client.Stat(testDir)
		assert.NoError(t, err)
		err = c.CheckParentDirs(testDir)
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	u := getTestUser()
	u.Permissions["/"] = []string{dataprovider.PermUpload, dataprovider.PermListItems, dataprovider.PermDownload}
	user, _, err = httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		c := common.NewBaseConnection(xid.New().String(), common.ProtocolSFTP, "", "", user)
		err = c.CheckParentDirs(testDir)
		assert.ErrorIs(t, err, sftp.ErrSSHFxPermissionDenied)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestPermissionErrors(t *testing.T) {
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	u := getTestSFTPUser()
	subDir := "/sub"
	u.Permissions[subDir] = nil
	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.MkdirAll(path.Join(subDir, subDir))
		assert.NoError(t, err)
		f, err := client.Create(path.Join(subDir, subDir, testFileName))
		if assert.NoError(t, err) {
			_, err = f.Write(testFileContent)
			assert.NoError(t, err)
			err = f.Close()
			assert.NoError(t, err)
		}
	}
	conn, client, err = getSftpClient(sftpUser)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		assert.NoError(t, checkBasicSFTP(client))
		_, err = client.ReadDir(subDir)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Mkdir(path.Join(subDir, subDir))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.RemoveDirectory(path.Join(subDir, subDir))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Symlink("test", path.Join(subDir, subDir))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Chmod(path.Join(subDir, subDir), os.ModePerm)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Chown(path.Join(subDir, subDir), os.Getuid(), os.Getgid())
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Chtimes(path.Join(subDir, subDir), time.Now(), time.Now())
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Truncate(path.Join(subDir, subDir), 0)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Remove(path.Join(subDir, subDir, testFileName))
		assert.ErrorIs(t, err, os.ErrPermission)
	}
	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestHiddenPatternFilter(t *testing.T) {
	deniedDir := "/denied_hidden"
	u := getTestUser()
	u.Filters.FilePatterns = []sdk.PatternsFilter{
		{
			Path:           deniedDir,
			DeniedPatterns: []string{"*.txt", "beta*"},
			DenyPolicy:     sdk.DenyPolicyHide,
		},
	}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	dirName := "beta"
	subDirName := "testDir"
	testFile := filepath.Join(u.GetHomeDir(), deniedDir, "file.txt")
	testFile1 := filepath.Join(u.GetHomeDir(), deniedDir, "beta.txt")
	testHiddenFile := filepath.Join(u.GetHomeDir(), deniedDir, dirName, subDirName, "hidden.jpg")
	err = os.MkdirAll(filepath.Join(u.GetHomeDir(), deniedDir), os.ModePerm)
	assert.NoError(t, err)
	err = os.WriteFile(testFile, testFileContent, os.ModePerm)
	assert.NoError(t, err)
	err = os.WriteFile(testFile1, testFileContent, os.ModePerm)
	assert.NoError(t, err)
	err = os.MkdirAll(filepath.Join(u.GetHomeDir(), deniedDir, dirName, subDirName), os.ModePerm)
	assert.NoError(t, err)
	err = os.WriteFile(testHiddenFile, testFileContent, os.ModePerm)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		files, err := client.ReadDir(deniedDir)
		assert.NoError(t, err)
		assert.Len(t, files, 0)
		err = client.Remove(path.Join(deniedDir, filepath.Base(testFile)))
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = client.Chtimes(path.Join(deniedDir, filepath.Base(testFile)), time.Now(), time.Now())
		assert.ErrorIs(t, err, os.ErrNotExist)
		_, err = client.Stat(path.Join(deniedDir, filepath.Base(testFile1)))
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = client.RemoveDirectory(path.Join(deniedDir, dirName))
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = client.Rename(path.Join(deniedDir, dirName), path.Join(deniedDir, "newname"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Mkdir(path.Join(deniedDir, "beta1"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = writeSFTPFile(path.Join(deniedDir, "afile.txt"), 1024, client)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = writeSFTPFile(path.Join(deniedDir, dirName, subDirName, "afile.jpg"), 1024, client)
		assert.ErrorIs(t, err, os.ErrPermission)
		_, err = client.Open(path.Join(deniedDir, dirName, subDirName, filepath.Base(testHiddenFile)))
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = client.Symlink(path.Join(deniedDir, dirName), dirName)
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = writeSFTPFile(path.Join(deniedDir, testFileName), 1024, client)
		assert.NoError(t, err)
		err = client.Symlink(path.Join(deniedDir, testFileName), path.Join(deniedDir, "symlink.txt"))
		assert.ErrorIs(t, err, os.ErrPermission)
		files, err = client.ReadDir(deniedDir)
		assert.NoError(t, err)
		assert.Len(t, files, 1)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	u.Filters.FilePatterns = []sdk.PatternsFilter{
		{
			Path:           deniedDir,
			DeniedPatterns: []string{"*.txt", "beta*"},
			DenyPolicy:     sdk.DenyPolicyDefault,
		},
	}
	user, _, err = httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		files, err := client.ReadDir(deniedDir)
		assert.NoError(t, err)
		assert.Len(t, files, 4)
		_, err = client.Stat(path.Join(deniedDir, filepath.Base(testFile)))
		assert.NoError(t, err)
		err = client.Chtimes(path.Join(deniedDir, filepath.Base(testFile)), time.Now(), time.Now())
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Mkdir(path.Join(deniedDir, "beta2"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = writeSFTPFile(path.Join(deniedDir, "afile2.txt"), 1024, client)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Symlink(path.Join(deniedDir, testFileName), path.Join(deniedDir, "link.txt"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = writeSFTPFile(path.Join(deniedDir, dirName, subDirName, "afile.jpg"), 1024, client)
		assert.NoError(t, err)
		f, err := client.Open(path.Join(deniedDir, dirName, subDirName, filepath.Base(testHiddenFile)))
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestHiddenRoot(t *testing.T) {
	// only the "/ftp" directory is allowed and visibile in the "/" path
	// within /ftp any file/directory is allowed and visibile
	u := getTestUser()
	u.Filters.FilePatterns = []sdk.PatternsFilter{
		{
			Path:            "/",
			AllowedPatterns: []string{"ftp"},
			DenyPolicy:      sdk.DenyPolicyHide,
		},
		{
			Path:            "/ftp",
			AllowedPatterns: []string{"*"},
		},
	}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	for i := 0; i < 10; i++ {
		err = os.MkdirAll(filepath.Join(user.HomeDir, fmt.Sprintf("ftp%d", i)), os.ModePerm)
		assert.NoError(t, err)
	}
	err = os.WriteFile(filepath.Join(user.HomeDir, testFileName), []byte(""), 0666)
	assert.NoError(t, err)
	err = os.WriteFile(filepath.Join(user.HomeDir, "ftp.txt"), []byte(""), 0666)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.Mkdir("ftp")
		assert.NoError(t, err)
		entries, err := client.ReadDir("/")
		assert.NoError(t, err)
		if assert.Len(t, entries, 1) {
			assert.Equal(t, "ftp", entries[0].Name())
		}
		_, err = client.Stat(".")
		assert.NoError(t, err)
		for _, name := range []string{testFileName, "ftp.txt"} {
			_, err = client.Stat(name)
			assert.ErrorIs(t, err, os.ErrNotExist)
		}
		for i := 0; i < 10; i++ {
			_, err = client.Stat(fmt.Sprintf("ftp%d", i))
			assert.ErrorIs(t, err, os.ErrNotExist)
		}
		err = writeSFTPFile(testFileName, 4096, client)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = writeSFTPFile("ftp123", 4096, client)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(testFileName, testFileName+"_rename")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = writeSFTPFile(path.Join("/ftp", testFileName), 4096, client)
		assert.NoError(t, err)
		err = client.Mkdir("/ftp/dir")
		assert.NoError(t, err)
		err = client.Rename(path.Join("/ftp", testFileName), path.Join("/ftp/dir", testFileName))
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestFileNotAllowedErrors(t *testing.T) {
	deniedDir := "/denied"
	u := getTestUser()
	u.Filters.FilePatterns = []sdk.PatternsFilter{
		{
			Path:           deniedDir,
			DeniedPatterns: []string{"*.txt"},
		},
	}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFile := filepath.Join(u.GetHomeDir(), deniedDir, "file.txt")
		err = os.MkdirAll(filepath.Join(u.GetHomeDir(), deniedDir), os.ModePerm)
		assert.NoError(t, err)
		err = os.WriteFile(testFile, testFileContent, os.ModePerm)
		assert.NoError(t, err)
		err = client.Remove(path.Join(deniedDir, "file.txt"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(path.Join(deniedDir, "file.txt"), path.Join(deniedDir, "file1.txt"))
		assert.ErrorIs(t, err, os.ErrPermission)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestRootDirVirtualFolder(t *testing.T) {
	mappedPath1 := filepath.Join(os.TempDir(), "mapped1")
	f1 := vfs.BaseVirtualFolder{
		Name:       filepath.Base(mappedPath1),
		MappedPath: mappedPath1,
		FsConfig: vfs.Filesystem{
			Provider: sdk.CryptedFilesystemProvider,
			CryptConfig: vfs.CryptFsConfig{
				Passphrase: kms.NewPlainSecret("cryptsecret"),
			},
		},
	}
	mappedPath2 := filepath.Join(os.TempDir(), "mapped2")
	f2 := vfs.BaseVirtualFolder{
		Name:       filepath.Base(mappedPath2),
		MappedPath: mappedPath2,
	}
	folder1, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	folder2, _, err := httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	u := getTestUser()
	u.QuotaFiles = 1000
	u.UploadDataTransfer = 1000
	u.DownloadDataTransfer = 5000
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folder1.Name,
		},
		VirtualPath: "/",
		QuotaFiles:  1000,
	})
	vdirPath2 := "/vmapped"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folder2.Name,
		},
		VirtualPath: vdirPath2,
		QuotaFiles:  -1,
		QuotaSize:   -1,
	})
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	f, err := user.GetVirtualFolderForPath("/")
	assert.NoError(t, err)
	assert.Equal(t, "/", f.VirtualPath)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
		f, err := client.Create(testFileName)
		if assert.NoError(t, err) {
			_, err = f.Write(testFileContent)
			assert.NoError(t, err)
			err = f.Close()
			assert.NoError(t, err)
		}
		assert.NoFileExists(t, filepath.Join(user.HomeDir, testFileName))
		assert.FileExists(t, filepath.Join(mappedPath1, testFileName))
		entries, err := client.ReadDir(".")
		if assert.NoError(t, err) {
			assert.Len(t, entries, 2)
		}
		user, _, err := httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 0, user.UsedQuotaFiles)
		folder, _, err := httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, folder.UsedQuotaFiles)
		f, err = client.Create(path.Join(vdirPath2, testFileName))
		if assert.NoError(t, err) {
			_, err = f.Write(testFileContent)
			assert.NoError(t, err)
			err = f.Close()
			assert.NoError(t, err)
		}
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		folder, _, err = httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, folder.UsedQuotaFiles)
		err = client.Rename(testFileName, path.Join(vdirPath2, testFileName+"_rename"))
		assert.Error(t, err)
		err = client.Rename(path.Join(vdirPath2, testFileName), testFileName+"_rename")
		assert.Error(t, err)
		err = client.Rename(testFileName, testFileName+"_rename")
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testFileName+"_rename"))
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folder1.Name}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath1)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folder2.Name}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath2)
	assert.NoError(t, err)
}
func TestTruncateQuotaLimits(t *testing.T) {
	mappedPath1 := filepath.Join(os.TempDir(), "mapped1")
	f1 := vfs.BaseVirtualFolder{
		Name:       filepath.Base(mappedPath1),
		MappedPath: mappedPath1,
	}
	folder1, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	mappedPath2 := filepath.Join(os.TempDir(), "mapped2")
	f2 := vfs.BaseVirtualFolder{
		Name:       filepath.Base(mappedPath2),
		MappedPath: mappedPath2,
	}
	folder2, _, err := httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	u := getTestUser()
	u.QuotaSize = 20
	u.UploadDataTransfer = 1000
	u.DownloadDataTransfer = 5000
	vdirPath1 := "/vmapped1"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folder1.Name,
		},
		VirtualPath: vdirPath1,
		QuotaFiles:  10,
	})
	vdirPath2 := "/vmapped2"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folder2.Name,
		},
		VirtualPath: vdirPath2,
		QuotaFiles:  -1,
		QuotaSize:   -1,
	})
	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	u = getTestSFTPUser()
	u.QuotaSize = 20
	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	for _, user := range []dataprovider.User{localUser, sftpUser} {
		conn, client, err := getSftpClient(user)
		if assert.NoError(t, err) {
			defer conn.Close()
			defer client.Close()
			f, err := client.OpenFile(testFileName, os.O_WRONLY|os.O_CREATE)
			if assert.NoError(t, err) {
				n, err := f.Write(testFileContent)
				assert.NoError(t, err)
				assert.Equal(t, len(testFileContent), n)
				err = f.Truncate(2)
				assert.NoError(t, err)
				expectedQuotaFiles := 0
				expectedQuotaSize := int64(2)
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
				assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
				_, err = f.Seek(expectedQuotaSize, io.SeekStart)
				assert.NoError(t, err)
				n, err = f.Write(testFileContent)
				assert.NoError(t, err)
				assert.Equal(t, len(testFileContent), n)
				err = f.Truncate(5)
				assert.NoError(t, err)
				expectedQuotaSize = int64(5)
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
				assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
				_, err = f.Seek(expectedQuotaSize, io.SeekStart)
				assert.NoError(t, err)
				n, err = f.Write(testFileContent)
				assert.NoError(t, err)
				assert.Equal(t, len(testFileContent), n)
				err = f.Close()
				assert.NoError(t, err)
				expectedQuotaFiles = 1
				expectedQuotaSize = int64(5) + int64(len(testFileContent))
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
				assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
			}
			// now truncate by path
			err = client.Truncate(testFileName, 5)
			assert.NoError(t, err)
			user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
			assert.NoError(t, err)
			assert.Equal(t, 1, user.UsedQuotaFiles)
			assert.Equal(t, int64(5), user.UsedQuotaSize)
			// now open an existing file without truncate it, quota should not change
			f, err = client.OpenFile(testFileName, os.O_WRONLY)
			if assert.NoError(t, err) {
				err = f.Close()
				assert.NoError(t, err)
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, 1, user.UsedQuotaFiles)
				assert.Equal(t, int64(5), user.UsedQuotaSize)
			}
			// open the file truncating it
			f, err = client.OpenFile(testFileName, os.O_WRONLY|os.O_TRUNC)
			if assert.NoError(t, err) {
				err = f.Close()
				assert.NoError(t, err)
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, 1, user.UsedQuotaFiles)
				assert.Equal(t, int64(0), user.UsedQuotaSize)
			}
			// now test max write size
			f, err = client.OpenFile(testFileName, os.O_WRONLY)
			if assert.NoError(t, err) {
				n, err := f.Write(testFileContent)
				assert.NoError(t, err)
				assert.Equal(t, len(testFileContent), n)
				err = f.Truncate(11)
				assert.NoError(t, err)
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, 1, user.UsedQuotaFiles)
				assert.Equal(t, int64(11), user.UsedQuotaSize)
				_, err = f.Seek(int64(11), io.SeekStart)
				assert.NoError(t, err)
				n, err = f.Write(testFileContent)
				assert.NoError(t, err)
				assert.Equal(t, len(testFileContent), n)
				err = f.Truncate(5)
				assert.NoError(t, err)
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, 1, user.UsedQuotaFiles)
				assert.Equal(t, int64(5), user.UsedQuotaSize)
				_, err = f.Seek(int64(5), io.SeekStart)
				assert.NoError(t, err)
				n, err = f.Write(testFileContent)
				assert.NoError(t, err)
				assert.Equal(t, len(testFileContent), n)
				err = f.Truncate(12)
				assert.NoError(t, err)
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, 1, user.UsedQuotaFiles)
				assert.Equal(t, int64(12), user.UsedQuotaSize)
				_, err = f.Seek(int64(12), io.SeekStart)
				assert.NoError(t, err)
				_, err = f.Write(testFileContent)
				if assert.Error(t, err) {
					assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
				}
				err = f.Close()
				assert.Error(t, err)
				// the file is deleted
				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)
			}
			if user.Username == defaultUsername {
				// basic test inside a virtual folder
				vfileName1 := path.Join(vdirPath1, testFileName)
				f, err = client.OpenFile(vfileName1, os.O_WRONLY|os.O_CREATE)
				if assert.NoError(t, err) {
					n, err := f.Write(testFileContent)
					assert.NoError(t, err)
					assert.Equal(t, len(testFileContent), n)
					err = f.Truncate(2)
					assert.NoError(t, err)
					expectedQuotaFiles := 0
					expectedQuotaSize := int64(2)
					fold, _, err := httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
					assert.NoError(t, err)
					assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
					assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
					err = f.Close()
					assert.NoError(t, err)
					expectedQuotaFiles = 1
					fold, _, err = httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
					assert.NoError(t, err)
					assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
					assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
				}
				err = client.Truncate(vfileName1, 1)
				assert.NoError(t, err)
				fold, _, err := httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, int64(1), fold.UsedQuotaSize)
				assert.Equal(t, 1, fold.UsedQuotaFiles)
				// now test on vdirPath2, the folder quota is included in the user's quota
				vfileName2 := path.Join(vdirPath2, testFileName)
				f, err = client.OpenFile(vfileName2, os.O_WRONLY|os.O_CREATE)
				if assert.NoError(t, err) {
					n, err := f.Write(testFileContent)
					assert.NoError(t, err)
					assert.Equal(t, len(testFileContent), n)
					err = f.Truncate(3)
					assert.NoError(t, err)
					expectedQuotaFiles := 0
					expectedQuotaSize := int64(3)
					fold, _, err := httpdtest.GetFolderByName(folder2.Name, http.StatusOK)
					assert.NoError(t, err)
					assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
					assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
					err = f.Close()
					assert.NoError(t, err)
					expectedQuotaFiles = 1
					fold, _, err = httpdtest.GetFolderByName(folder2.Name, http.StatusOK)
					assert.NoError(t, err)
					assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
					assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
					user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
					assert.NoError(t, err)
					assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
					assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
				}
				// cleanup
				err = os.RemoveAll(user.GetHomeDir())
				assert.NoError(t, err)
				if user.Username == defaultUsername {
					_, err = httpdtest.RemoveUser(user, http.StatusOK)
					assert.NoError(t, err)
					user.Password = defaultPassword
					user.QuotaSize = 0
					user.ID = 0
					user.CreatedAt = 0
					_, resp, err := httpdtest.AddUser(user, http.StatusCreated)
					assert.NoError(t, err, string(resp))
				}
			}
		}
	}
	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(localUser.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(folder1, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath1)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(folder2, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath2)
	assert.NoError(t, err)
}
func TestVirtualFoldersQuotaRenameOverwrite(t *testing.T) {
	testFileSize := int64(131072)
	testFileSize1 := int64(65537)
	testFileName1 := "test_file1.dat" //nolint:goconst
	u := getTestUser()
	u.QuotaFiles = 0
	u.QuotaSize = 0
	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
	folderName1 := filepath.Base(mappedPath1)
	vdirPath1 := "/vdir1" //nolint:goconst
	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
	folderName2 := filepath.Base(mappedPath2)
	vdirPath2 := "/vdir2" //nolint:goconst
	mappedPath3 := filepath.Join(os.TempDir(), "vdir3")
	folderName3 := filepath.Base(mappedPath3)
	vdirPath3 := "/vdir3"
	f1 := vfs.BaseVirtualFolder{
		Name:       folderName1,
		MappedPath: mappedPath1,
	}
	_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name:       folderName2,
		MappedPath: mappedPath2,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	f3 := vfs.BaseVirtualFolder{
		Name:       folderName3,
		MappedPath: mappedPath3,
	}
	_, _, err = httpdtest.AddFolder(f3, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName1,
		},
		VirtualPath: vdirPath1,
		QuotaFiles:  2,
		QuotaSize:   0,
	})
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName2,
		},
		VirtualPath: vdirPath2,
		QuotaFiles:  0,
		QuotaSize:   testFileSize + testFileSize1 + 1,
	})
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName3,
		},
		VirtualPath: vdirPath3,
		QuotaFiles:  2,
		QuotaSize:   testFileSize * 2,
	})
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		f, err := client.Open(path.Join(vdirPath1, testFileName))
		assert.NoError(t, err)
		contents, err := io.ReadAll(f)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		assert.Len(t, contents, int(testFileSize))
		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName1, testFileSize1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath3, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath3, testFileName+"1"), testFileSize, client)
		assert.NoError(t, err)
		err = client.Rename(testFileName, path.Join(vdirPath1, testFileName+".rename"))
		assert.Error(t, err)
		// we overwrite an existing file and we have unlimited size
		err = client.Rename(testFileName, path.Join(vdirPath1, testFileName))
		assert.NoError(t, err)
		// we have no space and we try to overwrite a bigger file with a smaller one, this should succeed
		err = client.Rename(testFileName1, path.Join(vdirPath2, testFileName))
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		// we have no space and we try to overwrite a smaller file with a bigger one, this should fail
		err = client.Rename(testFileName, path.Join(vdirPath2, testFileName1))
		assert.Error(t, err)
		fi, err := client.Stat(path.Join(vdirPath1, testFileName1))
		if assert.NoError(t, err) {
			assert.Equal(t, testFileSize1, fi.Size())
		}
		// we are overquota inside vdir3 size 2/2 and size 262144/262144
		err = client.Rename(path.Join(vdirPath1, testFileName1), path.Join(vdirPath3, testFileName1+".rename"))
		assert.Error(t, err)
		// we overwrite an existing file and we have enough size
		err = client.Rename(path.Join(vdirPath1, testFileName1), path.Join(vdirPath3, testFileName))
		assert.NoError(t, err)
		testFileName2 := "test_file2.dat"
		err = writeSFTPFile(testFileName2, testFileSize+testFileSize1, client)
		assert.NoError(t, err)
		// we overwrite an existing file and we haven't enough size
		err = client.Rename(testFileName2, path.Join(vdirPath3, testFileName))
		assert.Error(t, err)
		// now remove a file from vdir3, create a dir with 2 files and try to rename it in vdir3
		// this will fail since the rename will result in 3 files inside vdir3 and quota limits only
		// allow 2 total files there
		err = client.Remove(path.Join(vdirPath3, testFileName+"1"))
		assert.NoError(t, err)
		aDir := "a dir"
		err = client.Mkdir(aDir)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(aDir, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(aDir, testFileName1+"1"), testFileSize1, client)
		assert.NoError(t, err)
		err = client.Rename(aDir, path.Join(vdirPath3, aDir))
		assert.Error(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName3}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath1)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath2)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath3)
	assert.NoError(t, err)
}
func TestQuotaRenameOverwrite(t *testing.T) {
	u := getTestUser()
	u.QuotaFiles = 100
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileSize := int64(131072)
		testFileSize1 := int64(65537)
		testFileName1 := "test_file1.dat"
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		f, err := client.Open(testFileName)
		assert.NoError(t, err)
		contents := make([]byte, testFileSize)
		n, err := io.ReadFull(f, contents)
		assert.NoError(t, err)
		assert.Equal(t, int(testFileSize), n)
		err = f.Close()
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName1, testFileSize1, client)
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), user.UsedDownloadDataTransfer)
		assert.Equal(t, int64(0), user.UsedUploadDataTransfer)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
		err = client.Rename(testFileName, testFileName1)
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), user.UsedDownloadDataTransfer)
		assert.Equal(t, int64(0), user.UsedUploadDataTransfer)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize, user.UsedQuotaSize)
		err = client.Remove(testFileName1)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName1, testFileSize1, client)
		assert.NoError(t, err)
		err = client.Rename(testFileName1, testFileName)
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestVirtualFoldersQuotaValues(t *testing.T) {
	u := getTestUser()
	u.QuotaFiles = 100
	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
	vdirPath1 := "/vdir1"
	folderName1 := filepath.Base(mappedPath1)
	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
	vdirPath2 := "/vdir2"
	folderName2 := filepath.Base(mappedPath2)
	f1 := vfs.BaseVirtualFolder{
		Name:       folderName1,
		MappedPath: mappedPath1,
	}
	_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name:       folderName2,
		MappedPath: mappedPath2,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName1,
		},
		VirtualPath: vdirPath1,
		// quota is included in the user's one
		QuotaFiles: -1,
		QuotaSize:  -1,
	})
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName2,
		},
		VirtualPath: vdirPath2,
		// quota is unlimited and excluded from user's one
		QuotaFiles: 0,
		QuotaSize:  0,
	})
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileSize := int64(131072)
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		// we copy the same file two times to test quota update on file overwrite
		err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
		assert.NoError(t, err)
		expectedQuotaFiles := 2
		expectedQuotaSize := testFileSize * 2
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
		assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		err = client.Remove(path.Join(vdirPath1, testFileName))
		assert.NoError(t, err)
		err = client.Remove(path.Join(vdirPath2, testFileName))
		assert.NoError(t, err)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), f.UsedQuotaSize)
		assert.Equal(t, 0, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), f.UsedQuotaSize)
		assert.Equal(t, 0, f.UsedQuotaFiles)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath1)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath2)
	assert.NoError(t, err)
}
func TestQuotaRenameInsideSameVirtualFolder(t *testing.T) {
	u := getTestUser()
	u.QuotaFiles = 100
	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
	vdirPath1 := "/vdir1"
	folderName1 := filepath.Base(mappedPath1)
	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
	vdirPath2 := "/vdir2"
	folderName2 := filepath.Base(mappedPath2)
	f1 := vfs.BaseVirtualFolder{
		Name:       folderName1,
		MappedPath: mappedPath1,
	}
	_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name:       folderName2,
		MappedPath: mappedPath2,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName1,
		},
		VirtualPath: vdirPath1,
		// quota is included in the user's one
		QuotaFiles: -1,
		QuotaSize:  -1,
	})
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName2,
		},
		VirtualPath: vdirPath2,
		// quota is unlimited and excluded from user's one
		QuotaFiles: 0,
		QuotaSize:  0,
	})
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileName1 := "test_file1.dat"
		testFileSize := int64(131072)
		testFileSize1 := int64(65535)
		dir1 := "dir1" //nolint:goconst
		dir2 := "dir2" //nolint:goconst
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath1, dir1))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath1, dir2))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath2, dir1))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath2, dir2))
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		// initial files:
		// - vdir1/dir1/testFileName
		// - vdir1/dir2/testFileName1
		// - vdir2/dir1/testFileName
		// - vdir2/dir2/testFileName1
		//
		// rename a file inside vdir1 it is included inside user quota, so we have:
		// - vdir1/dir1/testFileName.rename
		// - vdir1/dir2/testFileName1
		// - vdir2/dir1/testFileName
		// - vdir2/dir2/testFileName1
		err = client.Rename(path.Join(vdirPath1, dir1, testFileName), path.Join(vdirPath1, dir1, testFileName+".rename"))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		// rename a file inside vdir2, it isn't included inside user quota, so we have:
		// - vdir1/dir1/testFileName.rename
		// - vdir1/dir2/testFileName1
		// - vdir2/dir1/testFileName.rename
		// - vdir2/dir2/testFileName1
		err = client.Rename(path.Join(vdirPath2, dir1, testFileName), path.Join(vdirPath2, dir1, testFileName+".rename"))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		// rename a file inside vdir2 overwriting an existing, we now have:
		// - vdir1/dir1/testFileName.rename
		// - vdir1/dir2/testFileName1
		// - vdir2/dir1/testFileName.rename (initial testFileName1)
		err = client.Rename(path.Join(vdirPath2, dir2, testFileName1), path.Join(vdirPath2, dir1, testFileName+".rename"))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		// rename a file inside vdir1 overwriting an existing, we now have:
		// - vdir1/dir1/testFileName.rename (initial testFileName1)
		// - vdir2/dir1/testFileName.rename (initial testFileName1)
		err = client.Rename(path.Join(vdirPath1, dir2, testFileName1), path.Join(vdirPath1, dir1, testFileName+".rename"))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		// rename a directory inside the same virtual folder, quota should not change
		err = client.RemoveDirectory(path.Join(vdirPath1, dir2))
		assert.NoError(t, err)
		err = client.RemoveDirectory(path.Join(vdirPath2, dir2))
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirPath1, dir1), path.Join(vdirPath1, dir2))
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirPath2, dir1), path.Join(vdirPath2, dir2))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath1)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath2)
	assert.NoError(t, err)
}
func TestQuotaRenameBetweenVirtualFolder(t *testing.T) {
	u := getTestUser()
	u.QuotaFiles = 100
	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
	folderName1 := filepath.Base(mappedPath1)
	vdirPath1 := "/vdir1"
	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
	folderName2 := filepath.Base(mappedPath2)
	vdirPath2 := "/vdir2"
	f1 := vfs.BaseVirtualFolder{
		Name:       folderName1,
		MappedPath: mappedPath1,
	}
	_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name:       folderName2,
		MappedPath: mappedPath2,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName1,
		},
		VirtualPath: vdirPath1,
		// quota is included in the user's one
		QuotaFiles: -1,
		QuotaSize:  -1,
	})
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName2,
		},
		VirtualPath: vdirPath2,
		// quota is unlimited and excluded from user's one
		QuotaFiles: 0,
		QuotaSize:  0,
	})
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileName1 := "test_file1.dat"
		testFileSize := int64(131072)
		testFileSize1 := int64(65535)
		dir1 := "dir1"
		dir2 := "dir2"
		err = client.Mkdir(path.Join(vdirPath1, dir1))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath1, dir2))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath2, dir1))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath2, dir2))
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		// initial files:
		// - vdir1/dir1/testFileName
		// - vdir1/dir2/testFileName1
		// - vdir2/dir1/testFileName
		// - vdir2/dir2/testFileName1
		//
		// rename a file from vdir1 to vdir2, vdir1 is included inside user quota, so we have:
		// - vdir1/dir1/testFileName
		// - vdir2/dir1/testFileName
		// - vdir2/dir2/testFileName1
		// - vdir2/dir1/testFileName1.rename
		err = client.Rename(path.Join(vdirPath1, dir2, testFileName1), path.Join(vdirPath2, dir1, testFileName1+".rename"))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize, user.UsedQuotaSize)
		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 3, f.UsedQuotaFiles)
		// rename a file from vdir2 to vdir1, vdir2 is not included inside user quota, so we have:
		// - vdir1/dir1/testFileName
		// - vdir1/dir2/testFileName.rename
		// - vdir2/dir2/testFileName1
		// - vdir2/dir1/testFileName1.rename
		err = client.Rename(path.Join(vdirPath2, dir1, testFileName), path.Join(vdirPath1, dir2, testFileName+".rename"))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize*2, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize*2, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1*2, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		// rename a file from vdir1 to vdir2 overwriting an existing file, vdir1 is included inside user quota, so we have:
		// - vdir1/dir2/testFileName.rename
		// - vdir2/dir2/testFileName1 (is the initial testFileName)
		// - vdir2/dir1/testFileName1.rename
		err = client.Rename(path.Join(vdirPath1, dir1, testFileName), path.Join(vdirPath2, dir2, testFileName1))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1+testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		// rename a file from vdir2 to vdir1 overwriting an existing file, vdir2 is not included inside user quota, so we have:
		// - vdir1/dir2/testFileName.rename (is the initial testFileName1)
		// - vdir2/dir2/testFileName1 (is the initial testFileName)
		err = client.Rename(path.Join(vdirPath2, dir1, testFileName1+".rename"), path.Join(vdirPath1, dir2, testFileName+".rename"))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName), testFileSize1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName+"1.dupl"), testFileSize1, client)
		assert.NoError(t, err)
		err = client.RemoveDirectory(path.Join(vdirPath1, dir1))
		assert.NoError(t, err)
		err = client.RemoveDirectory(path.Join(vdirPath2, dir1))
		assert.NoError(t, err)
		// - vdir1/dir2/testFileName.rename (initial testFileName1)
		// - vdir1/dir2/testFileName
		// - vdir2/dir2/testFileName1 (initial testFileName)
		// - vdir2/dir2/testFileName (initial testFileName1)
		// - vdir2/dir2/testFileName1.dupl
		// rename directories between the two virtual folders
		err = client.Rename(path.Join(vdirPath2, dir2), path.Join(vdirPath1, dir1))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 5, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize1*3+testFileSize*2, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1*3+testFileSize*2, f.UsedQuotaSize)
		assert.Equal(t, 5, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), f.UsedQuotaSize)
		assert.Equal(t, 0, f.UsedQuotaFiles)
		// now move on vpath2
		err = client.Rename(path.Join(vdirPath1, dir2), path.Join(vdirPath2, dir1))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 3, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize1*2+testFileSize, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1*2+testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 3, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath1)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath2)
	assert.NoError(t, err)
}
func TestQuotaRenameFromVirtualFolder(t *testing.T) {
	u := getTestUser()
	u.QuotaFiles = 100
	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
	folderName1 := filepath.Base(mappedPath1)
	vdirPath1 := "/vdir1"
	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
	folderName2 := filepath.Base(mappedPath2)
	vdirPath2 := "/vdir2"
	f1 := vfs.BaseVirtualFolder{
		Name:       folderName1,
		MappedPath: mappedPath1,
	}
	_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name:       folderName2,
		MappedPath: mappedPath2,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName1,
		},
		VirtualPath: vdirPath1,
		// quota is included in the user's one
		QuotaFiles: -1,
		QuotaSize:  -1,
	})
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName2,
		},
		VirtualPath: vdirPath2,
		// quota is unlimited and excluded from user's one
		QuotaFiles: 0,
		QuotaSize:  0,
	})
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileName1 := "test_file1.dat"
		testFileSize := int64(131072)
		testFileSize1 := int64(65535)
		dir1 := "dir1"
		dir2 := "dir2"
		err = client.Mkdir(path.Join(vdirPath1, dir1))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath1, dir2))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath2, dir1))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath2, dir2))
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		// initial files:
		// - vdir1/dir1/testFileName
		// - vdir1/dir2/testFileName1
		// - vdir2/dir1/testFileName
		// - vdir2/dir2/testFileName1
		//
		// rename a file from vdir1 to the user home dir, vdir1 is included in user quota so we have:
		// - testFileName
		// - vdir1/dir2/testFileName1
		// - vdir2/dir1/testFileName
		// - vdir2/dir2/testFileName1
		err = client.Rename(path.Join(vdirPath1, dir1, testFileName), path.Join(testFileName))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		// rename a file from vdir2 to the user home dir, vdir2 is not included in user quota so we have:
		// - testFileName
		// - testFileName1
		// - vdir1/dir2/testFileName1
		// - vdir2/dir1/testFileName
		err = client.Rename(path.Join(vdirPath2, dir2, testFileName1), path.Join(testFileName1))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 3, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		// rename a file from vdir1 to the user home dir overwriting an existing file, vdir1 is included in user quota so we have:
		// - testFileName (initial testFileName1)
		// - testFileName1
		// - vdir2/dir1/testFileName
		err = client.Rename(path.Join(vdirPath1, dir2, testFileName1), path.Join(testFileName))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize1+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), f.UsedQuotaSize)
		assert.Equal(t, 0, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		// rename a file from vdir2 to the user home dir overwriting an existing file, vdir2 is not included in user quota so we have:
		// - testFileName (initial testFileName1)
		// - testFileName1 (initial testFileName)
		err = client.Rename(path.Join(vdirPath2, dir1, testFileName), path.Join(testFileName1))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), f.UsedQuotaSize)
		assert.Equal(t, 0, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), f.UsedQuotaSize)
		assert.Equal(t, 0, f.UsedQuotaFiles)
		// dir rename
		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		// - testFileName (initial testFileName1)
		// - testFileName1 (initial testFileName)
		// - vdir1/dir1/testFileName
		// - vdir1/dir1/testFileName1
		// - dir1/testFileName
		// - dir1/testFileName1
		err = client.Rename(path.Join(vdirPath2, dir1), dir1)
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 6, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize*3+testFileSize1*3, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 2, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), f.UsedQuotaSize)
		assert.Equal(t, 0, f.UsedQuotaFiles)
		// - testFileName (initial testFileName1)
		// - testFileName1 (initial testFileName)
		// - dir2/testFileName
		// - dir2/testFileName1
		// - dir1/testFileName
		// - dir1/testFileName1
		err = client.Rename(path.Join(vdirPath1, dir1), dir2)
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 6, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize*3+testFileSize1*3, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), f.UsedQuotaSize)
		assert.Equal(t, 0, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, int64(0), f.UsedQuotaSize)
		assert.Equal(t, 0, f.UsedQuotaFiles)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath1)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath2)
	assert.NoError(t, err)
}
func TestQuotaRenameToVirtualFolder(t *testing.T) {
	u := getTestUser()
	u.QuotaFiles = 100
	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
	folderName1 := filepath.Base(mappedPath1)
	vdirPath1 := "/vdir1"
	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
	folderName2 := filepath.Base(mappedPath2)
	vdirPath2 := "/vdir2"
	f1 := vfs.BaseVirtualFolder{
		Name:       folderName1,
		MappedPath: mappedPath1,
	}
	_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name:       folderName2,
		MappedPath: mappedPath2,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName1,
		},
		VirtualPath: vdirPath1,
		// quota is included in the user's one
		QuotaFiles: -1,
		QuotaSize:  -1,
	})
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName2,
		},
		VirtualPath: vdirPath2,
		// quota is unlimited and excluded from user's one
		QuotaFiles: 0,
		QuotaSize:  0,
	})
	u.Permissions[vdirPath1] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload,
		dataprovider.PermOverwrite, dataprovider.PermDelete, dataprovider.PermCreateSymlinks, dataprovider.PermCreateDirs,
		dataprovider.PermRename}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileName1 := "test_file1.dat"
		testFileSize := int64(131072)
		testFileSize1 := int64(65535)
		dir1 := "dir1"
		dir2 := "dir2"
		err = client.Mkdir(path.Join(vdirPath1, dir1))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath1, dir2))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath2, dir1))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath2, dir2))
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName1, testFileSize1, client)
		assert.NoError(t, err)
		// initial files:
		// - testFileName
		// - testFileName1
		//
		// rename a file from user home dir to vdir1, vdir1 is included in user quota so we have:
		// - testFileName
		// - /vdir1/dir1/testFileName1
		err = client.Rename(testFileName1, path.Join(vdirPath1, dir1, testFileName1))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		// rename a file from user home dir to vdir2, vdir2 is not included in user quota so we have:
		// - /vdir2/dir1/testFileName
		// - /vdir1/dir1/testFileName1
		err = client.Rename(testFileName, path.Join(vdirPath2, dir1, testFileName))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		// upload two new files to the user home dir so we have:
		// - testFileName
		// - testFileName1
		// - /vdir1/dir1/testFileName1
		// - /vdir2/dir1/testFileName
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName1, testFileSize1, client)
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 3, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1+testFileSize1, user.UsedQuotaSize)
		// rename a file from user home dir to vdir1 overwriting an existing file, vdir1 is included in user quota so we have:
		// - testFileName1
		// - /vdir1/dir1/testFileName1 (initial testFileName)
		// - /vdir2/dir1/testFileName
		err = client.Rename(testFileName, path.Join(vdirPath1, dir1, testFileName1))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		// rename a file from user home dir to vdir2 overwriting an existing file, vdir2 is not included in user quota so we have:
		// - /vdir1/dir1/testFileName1 (initial testFileName)
		// - /vdir2/dir1/testFileName (initial testFileName1)
		err = client.Rename(testFileName1, path.Join(vdirPath2, dir1, testFileName))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		err = client.Mkdir(dir1)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(dir1, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		// - /dir1/testFileName
		// - /dir1/testFileName1
		// - /vdir1/dir1/testFileName1 (initial testFileName)
		// - /vdir2/dir1/testFileName (initial testFileName1)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 3, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		// - /vdir1/adir/testFileName
		// - /vdir1/adir/testFileName1
		// - /vdir1/dir1/testFileName1 (initial testFileName)
		// - /vdir2/dir1/testFileName (initial testFileName1)
		err = client.Rename(dir1, path.Join(vdirPath1, "adir"))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 3, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize*2+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 3, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 1, f.UsedQuotaFiles)
		err = client.Mkdir(dir1)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(dir1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(dir1, testFileName1), testFileSize1, client)
		assert.NoError(t, err)
		// - /vdir1/adir/testFileName
		// - /vdir1/adir/testFileName1
		// - /vdir1/dir1/testFileName1 (initial testFileName)
		// - /vdir2/dir1/testFileName (initial testFileName1)
		// - /vdir2/adir/testFileName
		// - /vdir2/adir/testFileName1
		err = client.Rename(dir1, path.Join(vdirPath2, "adir"))
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 3, user.UsedQuotaFiles)
		assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize)
		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize*2+testFileSize1, f.UsedQuotaSize)
		assert.Equal(t, 3, f.UsedQuotaFiles)
		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, testFileSize1*2+testFileSize, f.UsedQuotaSize)
		assert.Equal(t, 3, f.UsedQuotaFiles)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath1)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath2)
	assert.NoError(t, err)
}
func TestTransferQuotaLimits(t *testing.T) {
	u := getTestUser()
	u.TotalDataTransfer = 1
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileSize := int64(524288)
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		f, err := client.Open(testFileName)
		assert.NoError(t, err)
		contents := make([]byte, testFileSize)
		n, err := io.ReadFull(f, contents)
		assert.NoError(t, err)
		assert.Equal(t, int(testFileSize), n)
		assert.Len(t, contents, int(testFileSize))
		err = f.Close()
		assert.NoError(t, err)
		_, err = client.Open(testFileName)
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
			assert.Contains(t, err.Error(), common.ErrReadQuotaExceeded.Error())
		}
		err = writeSFTPFile(testFileName, testFileSize, client)
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
			assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
		}
	}
	// test the limit while uploading/downloading
	user.TotalDataTransfer = 0
	user.UploadDataTransfer = 1
	user.DownloadDataTransfer = 1
	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileSize := int64(450000)
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		f, err := client.Open(testFileName)
		if assert.NoError(t, err) {
			_, err = io.Copy(io.Discard, f)
			assert.NoError(t, err)
			err = f.Close()
			assert.NoError(t, err)
		}
		f, err = client.Open(testFileName)
		if assert.NoError(t, err) {
			_, err = io.Copy(io.Discard, f)
			if assert.Error(t, err) {
				assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
				assert.Contains(t, err.Error(), common.ErrReadQuotaExceeded.Error())
			}
			err = f.Close()
			assert.Error(t, err)
		}
		err = writeSFTPFile(testFileName, testFileSize, client)
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
			assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
		}
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestVirtualFoldersLink(t *testing.T) {
	u := getTestUser()
	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
	folderName1 := filepath.Base(mappedPath1)
	vdirPath1 := "/vdir1"
	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
	folderName2 := filepath.Base(mappedPath2)
	vdirPath2 := "/vdir2"
	f1 := vfs.BaseVirtualFolder{
		Name:       folderName1,
		MappedPath: mappedPath1,
	}
	_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name:       folderName2,
		MappedPath: mappedPath2,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName1,
		},
		VirtualPath: vdirPath1,
		// quota is included in the user's one
		QuotaFiles: -1,
		QuotaSize:  -1,
	})
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName2,
		},
		VirtualPath: vdirPath2,
		// quota is unlimited and excluded from user's one
		QuotaFiles: 0,
		QuotaSize:  0,
	})
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileSize := int64(131072)
		testDir := "adir"
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath1, testDir))
		assert.NoError(t, err)
		err = client.Mkdir(path.Join(vdirPath2, testDir))
		assert.NoError(t, err)
		err = client.Symlink(testFileName, testFileName+".link")
		assert.NoError(t, err)
		err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath1, testFileName+".link"))
		assert.NoError(t, err)
		err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath1, testDir, testFileName+".link"))
		assert.NoError(t, err)
		err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testFileName+".link"))
		assert.NoError(t, err)
		err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testDir, testFileName+".link"))
		assert.NoError(t, err)
		err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath1, testFileName+".link1"))
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath1, testDir, testFileName+".link1"))
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testFileName+".link1"))
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testDir, testFileName+".link1"))
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Symlink(path.Join(vdirPath1, testFileName), testFileName+".link1")
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Symlink(path.Join(vdirPath2, testFileName), testFileName+".link1")
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath2, testDir, testFileName+".link1"))
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath1, testFileName+".link1"))
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Symlink("/", "/roolink")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Symlink(testFileName, "/")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Symlink(testFileName, vdirPath1)
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Symlink(vdirPath1, testFileName+".link2")
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath1)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath2)
	assert.NoError(t, err)
}
func TestCrossFolderRename(t *testing.T) {
	folder1 := "folder1"
	folder2 := "folder2"
	folder3 := "folder3"
	folder4 := "folder4"
	folder5 := "folder5"
	folder6 := "folder6"
	folder7 := "folder7"
	baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err, string(resp))
	f1 := vfs.BaseVirtualFolder{
		Name:       folder1,
		MappedPath: filepath.Join(os.TempDir(), folder1),
		FsConfig: vfs.Filesystem{
			Provider: sdk.CryptedFilesystemProvider,
			CryptConfig: vfs.CryptFsConfig{
				Passphrase: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name:       folder2,
		MappedPath: filepath.Join(os.TempDir(), folder2),
		FsConfig: vfs.Filesystem{
			Provider: sdk.CryptedFilesystemProvider,
			CryptConfig: vfs.CryptFsConfig{
				Passphrase: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	f3 := vfs.BaseVirtualFolder{
		Name:       folder3,
		MappedPath: filepath.Join(os.TempDir(), folder3),
		FsConfig: vfs.Filesystem{
			Provider: sdk.CryptedFilesystemProvider,
			CryptConfig: vfs.CryptFsConfig{
				Passphrase: kms.NewPlainSecret(defaultPassword + "mod"),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f3, http.StatusCreated)
	assert.NoError(t, err)
	f4 := vfs.BaseVirtualFolder{
		Name:       folder4,
		MappedPath: filepath.Join(os.TempDir(), folder4),
		FsConfig: vfs.Filesystem{
			Provider: sdk.SFTPFilesystemProvider,
			SFTPConfig: vfs.SFTPFsConfig{
				BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
					Endpoint: sftpServerAddr,
					Username: baseUser.Username,
					Prefix:   path.Join("/", folder4),
				},
				Password: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f4, http.StatusCreated)
	assert.NoError(t, err)
	f5 := vfs.BaseVirtualFolder{
		Name:       folder5,
		MappedPath: filepath.Join(os.TempDir(), folder5),
		FsConfig: vfs.Filesystem{
			Provider: sdk.SFTPFilesystemProvider,
			SFTPConfig: vfs.SFTPFsConfig{
				BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
					Endpoint: sftpServerAddr,
					Username: baseUser.Username,
					Prefix:   path.Join("/", folder5),
				},
				Password: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f5, http.StatusCreated)
	assert.NoError(t, err)
	f6 := vfs.BaseVirtualFolder{
		Name:       folder6,
		MappedPath: filepath.Join(os.TempDir(), folder6),
		FsConfig: vfs.Filesystem{
			Provider: sdk.SFTPFilesystemProvider,
			SFTPConfig: vfs.SFTPFsConfig{
				BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
					Endpoint: "127.0.0.1:4024",
					Username: baseUser.Username,
					Prefix:   path.Join("/", folder6),
				},
				Password: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f6, http.StatusCreated)
	assert.NoError(t, err)
	f7 := vfs.BaseVirtualFolder{
		Name:       folder7,
		MappedPath: filepath.Join(os.TempDir(), folder7),
		FsConfig: vfs.Filesystem{
			Provider: sdk.SFTPFilesystemProvider,
			SFTPConfig: vfs.SFTPFsConfig{
				BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
					Endpoint: sftpServerAddr,
					Username: baseUser.Username,
					Prefix:   path.Join("/", folder4),
				},
				Password: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f7, http.StatusCreated)
	assert.NoError(t, err)
	u := getCryptFsUser()
	u.VirtualFolders = []vfs.VirtualFolder{
		{
			BaseVirtualFolder: vfs.BaseVirtualFolder{
				Name: folder1,
			},
			VirtualPath: path.Join("/", folder1),
			QuotaSize:   -1,
			QuotaFiles:  -1,
		},
		{
			BaseVirtualFolder: vfs.BaseVirtualFolder{
				Name: folder2,
			},
			VirtualPath: path.Join("/", folder2),
			QuotaSize:   -1,
			QuotaFiles:  -1,
		},
		{
			BaseVirtualFolder: vfs.BaseVirtualFolder{
				Name: folder3,
			},
			VirtualPath: path.Join("/", folder3),
			QuotaSize:   -1,
			QuotaFiles:  -1,
		},
		{
			BaseVirtualFolder: vfs.BaseVirtualFolder{
				Name: folder4,
			},
			VirtualPath: path.Join("/", folder4),
			QuotaSize:   -1,
			QuotaFiles:  -1,
		},
		{
			BaseVirtualFolder: vfs.BaseVirtualFolder{
				Name: folder5,
			},
			VirtualPath: path.Join("/", folder5),
			QuotaSize:   -1,
			QuotaFiles:  -1,
		},
		{
			BaseVirtualFolder: vfs.BaseVirtualFolder{
				Name: folder6,
			},
			VirtualPath: path.Join("/", folder6),
			QuotaSize:   -1,
			QuotaFiles:  -1,
		},
		{
			BaseVirtualFolder: vfs.BaseVirtualFolder{
				Name: folder7,
			},
			VirtualPath: path.Join("/", folder7),
			QuotaSize:   -1,
			QuotaFiles:  -1,
		},
	}
	user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		subDir := "testSubDir"
		err = client.Mkdir(subDir)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(subDir, "afile.bin"), 64, client)
		assert.NoError(t, err)
		err = client.Rename(subDir, path.Join("/", folder1, subDir))
		assert.NoError(t, err)
		_, err = client.Stat(path.Join("/", folder1, subDir))
		assert.NoError(t, err)
		_, err = client.Stat(path.Join("/", folder1, subDir, "afile.bin"))
		assert.NoError(t, err)
		err = client.Rename(path.Join("/", folder1, subDir), path.Join("/", folder2, subDir))
		assert.NoError(t, err)
		_, err = client.Stat(path.Join("/", folder2, subDir))
		assert.NoError(t, err)
		_, err = client.Stat(path.Join("/", folder2, subDir, "afile.bin"))
		assert.NoError(t, err)
		err = client.Rename(path.Join("/", folder2, subDir), path.Join("/", folder3, subDir))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = writeSFTPFile(path.Join("/", folder3, "file.bin"), 64, client)
		assert.NoError(t, err)
		err = client.Rename(path.Join("/", folder3, "file.bin"), "/renamed.bin")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(path.Join("/", folder3, "file.bin"), path.Join("/", folder2, "/renamed.bin"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(path.Join("/", folder3, "file.bin"), path.Join("/", folder3, "/renamed.bin"))
		assert.NoError(t, err)
		err = writeSFTPFile("/afile.bin", 64, client)
		assert.NoError(t, err)
		err = client.Rename("afile.bin", path.Join("/", folder4, "afile_renamed.bin"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = writeSFTPFile(path.Join("/", folder4, "afile.bin"), 64, client)
		assert.NoError(t, err)
		err = client.Rename(path.Join("/", folder4, "afile.bin"), path.Join("/", folder5, "afile_renamed.bin"))
		assert.NoError(t, err)
		err = client.Rename(path.Join("/", folder5, "afile_renamed.bin"), path.Join("/", folder6, "afile_renamed.bin"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = writeSFTPFile(path.Join("/", folder4, "afile.bin"), 64, client)
		assert.NoError(t, err)
		_, err = client.Stat(path.Join("/", folder7, "afile.bin"))
		assert.NoError(t, err)
		err = client.Rename(path.Join("/", folder4, "afile.bin"), path.Join("/", folder7, "afile.bin"))
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(baseUser.GetHomeDir())
	assert.NoError(t, err)
	for _, folderName := range []string{folder1, folder2, folder3, folder4, folder5, folder6, folder7} {
		_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
		assert.NoError(t, err)
		err = os.RemoveAll(filepath.Join(os.TempDir(), folderName))
		assert.NoError(t, err)
	}
}
func TestDirs(t *testing.T) {
	u := getTestUser()
	mappedPath := filepath.Join(os.TempDir(), "vdir")
	folderName := filepath.Base(mappedPath)
	vdirPath := "/path/vdir"
	f := vfs.BaseVirtualFolder{
		Name:       folderName,
		MappedPath: mappedPath,
	}
	_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName,
		},
		VirtualPath: vdirPath,
	})
	u.Permissions["/subdir"] = []string{dataprovider.PermDownload, dataprovider.PermUpload,
		dataprovider.PermDelete, dataprovider.PermCreateDirs, dataprovider.PermRename, dataprovider.PermListItems}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		info, err := client.ReadDir("/")
		if assert.NoError(t, err) {
			if assert.Len(t, info, 1) {
				assert.Equal(t, "path", info[0].Name())
			}
		}
		fi, err := client.Stat(path.Dir(vdirPath))
		if assert.NoError(t, err) {
			assert.True(t, fi.IsDir())
		}
		err = client.RemoveDirectory("/")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.RemoveDirectory(vdirPath)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.RemoveDirectory(path.Dir(vdirPath))
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.Mkdir(vdirPath)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Mkdir("adir")
		assert.NoError(t, err)
		err = client.Rename("/adir", path.Dir(vdirPath))
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = client.MkdirAll("/subdir/adir")
		assert.NoError(t, err)
		err = client.Rename("adir", "subdir/adir")
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
		err = writeSFTPFile("/subdir/afile.bin", 64, client)
		assert.NoError(t, err)
		err = writeSFTPFile("/afile.bin", 32, client)
		assert.NoError(t, err)
		err = client.Rename("afile.bin", "subdir/afile.bin")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename("afile.bin", "subdir/afile1.bin")
		assert.NoError(t, err)
		err = client.Rename(path.Dir(vdirPath), "renamed_vdir")
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath)
	assert.NoError(t, err)
}
func TestCryptFsStat(t *testing.T) {
	user, _, err := httpdtest.AddUser(getCryptFsUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileSize := int64(4096)
		err = writeSFTPFile(testFileName, testFileSize, client)
		assert.NoError(t, err)
		info, err := client.Stat(testFileName)
		if assert.NoError(t, err) {
			assert.Equal(t, testFileSize, info.Size())
		}
		info, err = os.Stat(filepath.Join(user.HomeDir, testFileName))
		if assert.NoError(t, err) {
			assert.Greater(t, info.Size(), testFileSize)
		}
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestFsPermissionErrors(t *testing.T) {
	if runtime.GOOS == osWindows {
		t.Skip("this test is not available on Windows")
	}
	user, _, err := httpdtest.AddUser(getCryptFsUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testDir := "tDir"
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = os.Chmod(user.GetHomeDir(), 0111)
		assert.NoError(t, err)
		err = client.RemoveDirectory(testDir)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(testDir, testDir+"1")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = os.Chmod(user.GetHomeDir(), os.ModePerm)
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestRenameErrorOutsideHomeDir(t *testing.T) {
	if runtime.GOOS == osWindows {
		t.Skip("this test is not available on Windows")
	}
	oldUploadMode := common.Config.UploadMode
	oldTempPath := common.Config.TempPath
	common.Config.UploadMode = common.UploadModeAtomicWithResume
	common.Config.TempPath = filepath.Clean(os.TempDir())
	vfs.SetTempPath(common.Config.TempPath)
	u := getTestUser()
	u.QuotaFiles = 1000
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = os.Chmod(user.GetHomeDir(), 0555)
		assert.NoError(t, err)
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
		f, err := client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.ErrorIs(t, err, os.ErrPermission)
		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)
		err = os.Chmod(user.GetHomeDir(), os.ModeDir)
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	common.Config.UploadMode = oldUploadMode
	common.Config.TempPath = oldTempPath
	vfs.SetTempPath(oldTempPath)
}
func TestResolvePathError(t *testing.T) {
	u := getTestUser()
	u.HomeDir = "relative_path"
	conn := common.NewBaseConnection("", common.ProtocolFTP, "", "", u)
	testPath := "apath"
	_, err := conn.ListDir(testPath)
	assert.Error(t, err)
	err = conn.CreateDir(testPath, true)
	assert.Error(t, err)
	err = conn.RemoveDir(testPath)
	assert.Error(t, err)
	err = conn.Rename(testPath, testPath+"1")
	assert.Error(t, err)
	err = conn.CreateSymlink(testPath, testPath+".sym")
	assert.Error(t, err)
	_, err = conn.DoStat(testPath, 0, false)
	assert.Error(t, err)
	err = conn.RemoveAll(testPath)
	assert.Error(t, err)
	err = conn.SetStat(testPath, &common.StatAttributes{
		Atime: time.Now(),
		Mtime: time.Now(),
	})
	assert.Error(t, err)
	u = getTestUser()
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			MappedPath: "relative_mapped_path",
		},
		VirtualPath: "/vpath",
	})
	err = os.MkdirAll(u.HomeDir, os.ModePerm)
	assert.NoError(t, err)
	conn.User = u
	err = conn.Rename(testPath, "/vpath/subpath")
	assert.Error(t, err)
	outHomePath := filepath.Join(os.TempDir(), testFileName)
	err = os.WriteFile(outHomePath, testFileContent, os.ModePerm)
	assert.NoError(t, err)
	err = os.Symlink(outHomePath, filepath.Join(u.HomeDir, testFileName+".link"))
	assert.NoError(t, err)
	err = os.WriteFile(filepath.Join(u.HomeDir, testFileName), testFileContent, os.ModePerm)
	assert.NoError(t, err)
	err = conn.CreateSymlink(testFileName, testFileName+".link")
	assert.Error(t, err)
	err = os.RemoveAll(u.GetHomeDir())
	assert.NoError(t, err)
	err = os.Remove(outHomePath)
	assert.NoError(t, err)
}
func TestUserPasswordHashing(t *testing.T) {
	if config.GetProviderConf().Driver == dataprovider.MemoryDataProviderName {
		t.Skip("this test is not supported with the memory provider")
	}
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	err = dataprovider.Close()
	assert.NoError(t, err)
	err = config.LoadConfig(configDir, "")
	assert.NoError(t, err)
	providerConf := config.GetProviderConf()
	providerConf.PasswordHashing.Algo = dataprovider.HashingAlgoArgon2ID
	err = dataprovider.Initialize(providerConf, configDir, true)
	assert.NoError(t, err)
	currentUser, err := dataprovider.UserExists(user.Username, "")
	assert.NoError(t, err)
	assert.True(t, strings.HasPrefix(currentUser.Password, "$2a$"))
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	u = getTestUser()
	user, _, err = httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	currentUser, err = dataprovider.UserExists(user.Username, "")
	assert.NoError(t, err)
	assert.True(t, strings.HasPrefix(currentUser.Password, "$argon2id$"))
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = dataprovider.Close()
	assert.NoError(t, err)
	err = config.LoadConfig(configDir, "")
	assert.NoError(t, err)
	providerConf = config.GetProviderConf()
	err = dataprovider.Initialize(providerConf, configDir, true)
	assert.NoError(t, err)
}
func TestAllowList(t *testing.T) {
	configCopy := common.Config
	entries := []dataprovider.IPListEntry{
		{
			IPOrNet:   "172.18.1.1/32",
			Type:      dataprovider.IPListTypeAllowList,
			Mode:      dataprovider.ListModeAllow,
			Protocols: 0,
		},
		{
			IPOrNet:   "172.18.1.2/32",
			Type:      dataprovider.IPListTypeAllowList,
			Mode:      dataprovider.ListModeAllow,
			Protocols: 0,
		},
		{
			IPOrNet:   "10.8.7.0/24",
			Type:      dataprovider.IPListTypeAllowList,
			Mode:      dataprovider.ListModeAllow,
			Protocols: 5,
		},
		{
			IPOrNet:   "0.0.0.0/0",
			Type:      dataprovider.IPListTypeAllowList,
			Mode:      dataprovider.ListModeAllow,
			Protocols: 8,
		},
		{
			IPOrNet:   "::/0",
			Type:      dataprovider.IPListTypeAllowList,
			Mode:      dataprovider.ListModeAllow,
			Protocols: 8,
		},
	}
	for _, e := range entries {
		_, resp, err := httpdtest.AddIPListEntry(e, http.StatusCreated)
		assert.NoError(t, err, string(resp))
	}
	common.Config.AllowListStatus = 1
	err := common.Initialize(common.Config, 0)
	assert.NoError(t, err)
	assert.True(t, common.Config.IsAllowListEnabled())
	testIP := "172.18.1.1"
	assert.NoError(t, common.Connections.IsNewConnectionAllowed(testIP, common.ProtocolFTP))
	entry := entries[0]
	entry.Protocols = 1
	_, _, err = httpdtest.UpdateIPListEntry(entry, http.StatusOK)
	assert.NoError(t, err)
	assert.Error(t, common.Connections.IsNewConnectionAllowed(testIP, common.ProtocolFTP))
	assert.NoError(t, common.Connections.IsNewConnectionAllowed(testIP, common.ProtocolSSH))
	_, err = httpdtest.RemoveIPListEntry(entry, http.StatusOK)
	assert.NoError(t, err)
	entries = entries[1:]
	assert.Error(t, common.Connections.IsNewConnectionAllowed(testIP, common.ProtocolSSH))
	assert.Error(t, common.Connections.IsNewConnectionAllowed("172.18.1.3", common.ProtocolSSH))
	assert.NoError(t, common.Connections.IsNewConnectionAllowed("172.18.1.3", common.ProtocolHTTP))
	assert.NoError(t, common.Connections.IsNewConnectionAllowed("10.8.7.3", common.ProtocolWebDAV))
	assert.NoError(t, common.Connections.IsNewConnectionAllowed("10.8.7.4", common.ProtocolSSH))
	assert.Error(t, common.Connections.IsNewConnectionAllowed("10.8.7.4", common.ProtocolFTP))
	assert.NoError(t, common.Connections.IsNewConnectionAllowed("10.8.7.4", common.ProtocolHTTP))
	assert.NoError(t, common.Connections.IsNewConnectionAllowed("2001:0db8::1428:57ab", common.ProtocolHTTP))
	assert.Error(t, common.Connections.IsNewConnectionAllowed("2001:0db8::1428:57ab", common.ProtocolSSH))
	assert.Error(t, common.Connections.IsNewConnectionAllowed("10.8.8.2", common.ProtocolWebDAV))
	assert.Error(t, common.Connections.IsNewConnectionAllowed("invalid IP", common.ProtocolHTTP))
	common.Config = configCopy
	err = common.Initialize(common.Config, 0)
	assert.NoError(t, err)
	assert.False(t, common.Config.IsAllowListEnabled())
	for _, e := range entries {
		_, err := httpdtest.RemoveIPListEntry(e, http.StatusOK)
		assert.NoError(t, err)
	}
}
func TestDbDefenderErrors(t *testing.T) {
	if !isDbDefenderSupported() {
		t.Skip("this test is not supported with the current database provider")
	}
	configCopy := common.Config
	common.Config.DefenderConfig.Enabled = true
	common.Config.DefenderConfig.Driver = common.DefenderDriverProvider
	err := common.Initialize(common.Config, 0)
	assert.NoError(t, err)
	testIP := "127.1.1.1"
	hosts, err := common.GetDefenderHosts()
	assert.NoError(t, err)
	assert.Len(t, hosts, 0)
	common.AddDefenderEvent(testIP, common.ProtocolSSH, common.HostEventLimitExceeded)
	hosts, err = common.GetDefenderHosts()
	assert.NoError(t, err)
	assert.Len(t, hosts, 1)
	score, err := common.GetDefenderScore(testIP)
	assert.NoError(t, err)
	assert.Equal(t, 3, score)
	banTime, err := common.GetDefenderBanTime(testIP)
	assert.NoError(t, err)
	assert.Nil(t, banTime)
	err = dataprovider.Close()
	assert.NoError(t, err)
	common.AddDefenderEvent(testIP, common.ProtocolFTP, common.HostEventLimitExceeded)
	_, err = common.GetDefenderHosts()
	assert.Error(t, err)
	_, err = common.GetDefenderHost(testIP)
	assert.Error(t, err)
	_, err = common.GetDefenderBanTime(testIP)
	assert.Error(t, err)
	_, err = common.GetDefenderScore(testIP)
	assert.Error(t, err)
	err = config.LoadConfig(configDir, "")
	assert.NoError(t, err)
	providerConf := config.GetProviderConf()
	err = dataprovider.Initialize(providerConf, configDir, true)
	assert.NoError(t, err)
	err = dataprovider.CleanupDefender(util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Hour)))
	assert.NoError(t, err)
	common.Config = configCopy
	err = common.Initialize(common.Config, 0)
	assert.NoError(t, err)
}
func TestDelayedQuotaUpdater(t *testing.T) {
	err := dataprovider.Close()
	assert.NoError(t, err)
	err = config.LoadConfig(configDir, "")
	assert.NoError(t, err)
	providerConf := config.GetProviderConf()
	providerConf.DelayedQuotaUpdate = 120
	err = dataprovider.Initialize(providerConf, configDir, true)
	assert.NoError(t, err)
	u := getTestUser()
	u.QuotaFiles = 100
	u.TotalDataTransfer = 2000
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	err = dataprovider.UpdateUserQuota(&user, 10, 6000, false)
	assert.NoError(t, err)
	err = dataprovider.UpdateUserTransferQuota(&user, 100, 200, false)
	assert.NoError(t, err)
	files, size, ulSize, dlSize, err := dataprovider.GetUsedQuota(user.Username)
	assert.NoError(t, err)
	assert.Equal(t, 10, files)
	assert.Equal(t, int64(6000), size)
	assert.Equal(t, int64(100), ulSize)
	assert.Equal(t, int64(200), dlSize)
	userGet, err := dataprovider.UserExists(user.Username, "")
	assert.NoError(t, err)
	assert.Equal(t, 0, userGet.UsedQuotaFiles)
	assert.Equal(t, int64(0), userGet.UsedQuotaSize)
	assert.Equal(t, int64(0), userGet.UsedUploadDataTransfer)
	assert.Equal(t, int64(0), userGet.UsedDownloadDataTransfer)
	err = dataprovider.UpdateUserQuota(&user, 10, 6000, true)
	assert.NoError(t, err)
	err = dataprovider.UpdateUserTransferQuota(&user, 100, 200, true)
	assert.NoError(t, err)
	files, size, ulSize, dlSize, err = dataprovider.GetUsedQuota(user.Username)
	assert.NoError(t, err)
	assert.Equal(t, 10, files)
	assert.Equal(t, int64(6000), size)
	assert.Equal(t, int64(100), ulSize)
	assert.Equal(t, int64(200), dlSize)
	userGet, err = dataprovider.UserExists(user.Username, "")
	assert.NoError(t, err)
	assert.Equal(t, 10, userGet.UsedQuotaFiles)
	assert.Equal(t, int64(6000), userGet.UsedQuotaSize)
	assert.Equal(t, int64(100), userGet.UsedUploadDataTransfer)
	assert.Equal(t, int64(200), userGet.UsedDownloadDataTransfer)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	folder := vfs.BaseVirtualFolder{
		Name:       "folder",
		MappedPath: filepath.Join(os.TempDir(), "p"),
	}
	err = dataprovider.AddFolder(&folder, "", "", "")
	assert.NoError(t, err)
	err = dataprovider.UpdateVirtualFolderQuota(&folder, 10, 6000, false)
	assert.NoError(t, err)
	files, size, err = dataprovider.GetUsedVirtualFolderQuota(folder.Name)
	assert.NoError(t, err)
	assert.Equal(t, 10, files)
	assert.Equal(t, int64(6000), size)
	folderGet, err := dataprovider.GetFolderByName(folder.Name)
	assert.NoError(t, err)
	assert.Equal(t, 0, folderGet.UsedQuotaFiles)
	assert.Equal(t, int64(0), folderGet.UsedQuotaSize)
	err = dataprovider.UpdateVirtualFolderQuota(&folder, 10, 6000, true)
	assert.NoError(t, err)
	files, size, err = dataprovider.GetUsedVirtualFolderQuota(folder.Name)
	assert.NoError(t, err)
	assert.Equal(t, 10, files)
	assert.Equal(t, int64(6000), size)
	folderGet, err = dataprovider.GetFolderByName(folder.Name)
	assert.NoError(t, err)
	assert.Equal(t, 10, folderGet.UsedQuotaFiles)
	assert.Equal(t, int64(6000), folderGet.UsedQuotaSize)
	err = dataprovider.DeleteFolder(folder.Name, "", "", "")
	assert.NoError(t, err)
	err = dataprovider.Close()
	assert.NoError(t, err)
	err = config.LoadConfig(configDir, "")
	assert.NoError(t, err)
	providerConf = config.GetProviderConf()
	err = dataprovider.Initialize(providerConf, configDir, true)
	assert.NoError(t, err)
}
func TestPasswordCaching(t *testing.T) {
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	dbUser, err := dataprovider.UserExists(user.Username, "")
	assert.NoError(t, err)
	found, match := dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.False(t, found)
	assert.False(t, match)
	user.Password = "wrong"
	_, _, err = getSftpClient(user)
	assert.Error(t, err)
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.False(t, found)
	assert.False(t, match)
	user.Password = ""
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
	}
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.True(t, found)
	assert.True(t, match)
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword+"_", dbUser.Password)
	assert.True(t, found)
	assert.False(t, match)
	found, match = dataprovider.CheckCachedUserPassword(user.Username+"_", defaultPassword, dbUser.Password)
	assert.False(t, found)
	assert.False(t, match)
	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	// the password was not changed
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.True(t, found)
	assert.True(t, match)
	// the password hash will change
	user.Password = defaultPassword
	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	dbUser, err = dataprovider.UserExists(user.Username, "")
	assert.NoError(t, err)
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.False(t, found)
	assert.False(t, match)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
	}
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.True(t, found)
	assert.True(t, match)
	//change password
	newPassword := defaultPassword + "mod"
	user.Password = newPassword
	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	dbUser, err = dataprovider.UserExists(user.Username, "")
	assert.NoError(t, err)
	found, match = dataprovider.CheckCachedUserPassword(user.Username, newPassword, dbUser.Password)
	assert.False(t, found)
	assert.False(t, match)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
	}
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.True(t, found)
	assert.False(t, match)
	found, match = dataprovider.CheckCachedUserPassword(user.Username, newPassword, dbUser.Password)
	assert.True(t, found)
	assert.True(t, match)
	// update the password
	err = dataprovider.UpdateUserPassword(user.Username, defaultPassword, "", "", "")
	assert.NoError(t, err)
	dbUser, err = dataprovider.UserExists(user.Username, "")
	assert.NoError(t, err)
	// the stored hash does not match
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.False(t, found)
	assert.False(t, match)
	user.Password = defaultPassword
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = checkBasicSFTP(client)
		assert.NoError(t, err)
	}
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.True(t, found)
	assert.True(t, match)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
	assert.False(t, found)
	assert.False(t, match)
}
func TestEventRule(t *testing.T) {
	if runtime.GOOS == osWindows {
		t.Skip("this test is not available on Windows")
	}
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notification@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeHTTP,
		Options: dataprovider.BaseEventActionOptions{
			HTTPConfig: dataprovider.EventActionHTTPConfig{
				Endpoint: "http://localhost",
				Timeout:  20,
				Method:   http.MethodGet,
			},
		},
	}
	a2 := dataprovider.BaseEventAction{
		Name: "action2",
		Type: dataprovider.ActionTypeBackup,
	}
	a3 := dataprovider.BaseEventAction{
		Name: "action3",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"test1@example.com", "test2@example.com"},
				Bcc:        []string{"test3@example.com"},
				Subject:    `New "{{Event}}" from "{{Name}}" status {{StatusString}}`,
				Body:       "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} Data: {{ObjectData}} {{ErrorString}}",
			},
		},
	}
	a4 := dataprovider.BaseEventAction{
		Name: "action4",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"failure@example.com"},
				Subject:    `Failed "{{Event}}" from "{{Name}}"`,
				Body:       "Fs path {{FsPath}}, protocol: {{Protocol}}, IP: {{IP}} {{ErrorString}}",
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
	assert.NoError(t, err)
	action4, _, err := httpdtest.AddEventAction(a4, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test rule1",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
			Options: dataprovider.ConditionOptions{
				FsPaths: []dataprovider.ConditionPattern{
					{
						Pattern: "/subdir/*.dat",
					},
					{
						Pattern: "/**/*.txt",
					},
				},
			},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync:   true,
					StopOnFailure: true,
				},
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 2,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action3.Name,
				},
				Order: 3,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action4.Name,
				},
				Order: 4,
				Options: dataprovider.EventActionOptions{
					IsFailureAction: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	r2 := dataprovider.EventRule{
		Name:    "test rule2",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"download"},
			Options: dataprovider.ConditionOptions{
				FsPaths: []dataprovider.ConditionPattern{
					{
						Pattern: "/**/*.dat",
					},
				},
			},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action3.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action4.Name,
				},
				Order: 2,
				Options: dataprovider.EventActionOptions{
					IsFailureAction: true,
				},
			},
		},
	}
	rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
	assert.NoError(t, err)
	r3 := dataprovider.EventRule{
		Name:    "test rule3",
		Status:  1,
		Trigger: dataprovider.EventTriggerProviderEvent,
		Conditions: dataprovider.EventConditions{
			ProviderEvents: []string{"delete"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action3.Name,
				},
				Order: 1,
			},
		},
	}
	rule3, _, err := httpdtest.AddEventRule(r3, http.StatusCreated)
	assert.NoError(t, err)
	uploadScriptPath := filepath.Join(os.TempDir(), "upload.sh")
	u := getTestUser()
	u.DownloadDataTransfer = 1
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	movedFileName := "moved.dat"
	movedPath := filepath.Join(user.HomeDir, movedFileName)
	err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, "", 0), 0755)
	assert.NoError(t, err)
	action1.Type = dataprovider.ActionTypeCommand
	action1.Options = dataprovider.BaseEventActionOptions{
		CmdConfig: dataprovider.EventActionCommandConfig{
			Cmd:     uploadScriptPath,
			Timeout: 10,
			EnvVars: []dataprovider.KeyValue{
				{
					Key:   "SFTPGO_ACTION_PATH",
					Value: "{{FsPath}}",
				},
				{
					Key:   "CUSTOM_ENV_VAR",
					Value: "value",
				},
			},
		},
	}
	action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	dirName := "subdir"
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		size := int64(32768)
		// rule conditions does not match
		err = writeSFTPFileNoCheck(testFileName, size, client)
		assert.NoError(t, err)
		info, err := client.Stat(testFileName)
		if assert.NoError(t, err) {
			assert.Equal(t, size, info.Size())
		}
		err = client.Mkdir(dirName)
		assert.NoError(t, err)
		err = client.Mkdir("subdir1")
		assert.NoError(t, err)
		// rule conditions match
		lastReceivedEmail.reset()
		err = writeSFTPFileNoCheck(path.Join(dirName, testFileName), size, client)
		assert.NoError(t, err)
		_, err = client.Stat(path.Join(dirName, testFileName))
		assert.Error(t, err)
		info, err = client.Stat(movedFileName)
		if assert.NoError(t, err) {
			assert.Equal(t, size, info.Size())
		}
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3000*time.Millisecond, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 3)
		assert.True(t, util.Contains(email.To, "test1@example.com"))
		assert.True(t, util.Contains(email.To, "test2@example.com"))
		assert.True(t, util.Contains(email.To, "test3@example.com"))
		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "upload" from "%s" status OK`, user.Username))
		// test the failure action, we download a file that exceeds the transfer quota limit
		err = writeSFTPFileNoCheck(path.Join("subdir1", testFileName), 1*1024*1024+65535, client)
		assert.NoError(t, err)
		lastReceivedEmail.reset()
		f, err := client.Open(path.Join("subdir1", testFileName))
		assert.NoError(t, err)
		_, err = io.ReadAll(f)
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), common.ErrReadQuotaExceeded.Error())
		}
		err = f.Close()
		assert.Error(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3000*time.Millisecond, 100*time.Millisecond)
		email = lastReceivedEmail.get()
		assert.Len(t, email.To, 3)
		assert.True(t, util.Contains(email.To, "test1@example.com"))
		assert.True(t, util.Contains(email.To, "test2@example.com"))
		assert.True(t, util.Contains(email.To, "test3@example.com"))
		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s" status KO`, user.Username))
		assert.Contains(t, email.Data, `"download" failed`)
		assert.Contains(t, email.Data, common.ErrReadQuotaExceeded.Error())
		_, err = httpdtest.UpdateTransferQuotaUsage(user, "", http.StatusOK)
		assert.NoError(t, err)
		// remove the upload script to test the failure action
		err = os.Remove(uploadScriptPath)
		assert.NoError(t, err)
		lastReceivedEmail.reset()
		err = writeSFTPFileNoCheck(path.Join(dirName, testFileName), size, client)
		assert.Error(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3000*time.Millisecond, 100*time.Millisecond)
		email = lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, "failure@example.com"))
		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: Failed "upload" from "%s"`, user.Username))
		assert.Contains(t, email.Data, fmt.Sprintf(`action %q failed`, action1.Name))
		// now test the download rule
		lastReceivedEmail.reset()
		f, err = client.Open(movedFileName)
		assert.NoError(t, err)
		contents, err := io.ReadAll(f)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		assert.Len(t, contents, int(size))
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3000*time.Millisecond, 100*time.Millisecond)
		email = lastReceivedEmail.get()
		assert.Len(t, email.To, 3)
		assert.True(t, util.Contains(email.To, "test1@example.com"))
		assert.True(t, util.Contains(email.To, "test2@example.com"))
		assert.True(t, util.Contains(email.To, "test3@example.com"))
		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s"`, user.Username))
	}
	// test upload action command with arguments
	action1.Options.CmdConfig.Args = []string{"{{Event}}", "{{VirtualPath}}", "custom_arg"}
	action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	uploadLogFilePath := filepath.Join(os.TempDir(), "upload.log")
	err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, uploadLogFilePath, 0), 0755)
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = writeSFTPFileNoCheck(path.Join(dirName, testFileName), 123, client)
		assert.NoError(t, err)
		logContent, err := os.ReadFile(uploadLogFilePath)
		assert.NoError(t, err)
		assert.Equal(t, fmt.Sprintf("upload %s custom_arg", util.CleanPath(path.Join(dirName, testFileName))),
			strings.TrimSpace(string(logContent)))
		err = os.Remove(uploadLogFilePath)
		assert.NoError(t, err)
		lastReceivedEmail.reset()
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3000*time.Millisecond, 100*time.Millisecond)
	}
	lastReceivedEmail.reset()
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
	assert.NoError(t, err)
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email := lastReceivedEmail.get()
	assert.Len(t, email.To, 3)
	assert.True(t, util.Contains(email.To, "test1@example.com"))
	assert.True(t, util.Contains(email.To, "test2@example.com"))
	assert.True(t, util.Contains(email.To, "test3@example.com"))
	assert.Contains(t, email.Data, `Subject: New "delete" from "admin"`)
	_, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action4, http.StatusOK)
	assert.NoError(t, err)
	lastReceivedEmail.reset()
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventRuleProviderEvents(t *testing.T) {
	if runtime.GOOS == osWindows {
		t.Skip("this test is not available on Windows")
	}
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notification@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	saveObjectScriptPath := filepath.Join(os.TempDir(), "provider.sh")
	outPath := filepath.Join(os.TempDir(), "provider_out.json")
	err = os.WriteFile(saveObjectScriptPath, getSaveProviderObjectScriptContent(outPath, 0), 0755)
	assert.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeCommand,
		Options: dataprovider.BaseEventActionOptions{
			CmdConfig: dataprovider.EventActionCommandConfig{
				Cmd:     saveObjectScriptPath,
				Timeout: 10,
				EnvVars: []dataprovider.KeyValue{
					{
						Key:   "SFTPGO_OBJECT_DATA",
						Value: "{{ObjectData}}",
					},
				},
			},
		},
	}
	a2 := dataprovider.BaseEventAction{
		Name: "a2",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"test3@example.com"},
				Subject:    `New "{{Event}}" from "{{Name}}"`,
				Body:       "Object name: {{ObjectName}} object type: {{ObjectType}} Data: {{ObjectData}}",
			},
		},
	}
	a3 := dataprovider.BaseEventAction{
		Name: "a3",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"failure@example.com"},
				Subject:    `Failed "{{Event}}" from "{{Name}}"`,
				Body:       "Object name: {{ObjectName}} object type: {{ObjectType}}, IP: {{IP}}",
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
	assert.NoError(t, err)
	r := dataprovider.EventRule{
		Name:    "rule",
		Status:  1,
		Trigger: dataprovider.EventTriggerProviderEvent,
		Conditions: dataprovider.EventConditions{
			ProviderEvents: []string{"update"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					StopOnFailure: true,
				},
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 2,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action3.Name,
				},
				Order: 3,
				Options: dataprovider.EventActionOptions{
					IsFailureAction: true,
					StopOnFailure:   true,
				},
			},
		},
	}
	rule, _, err := httpdtest.AddEventRule(r, http.StatusCreated)
	assert.NoError(t, err)
	lastReceivedEmail.reset()
	// create and update a folder to trigger the rule
	folder := vfs.BaseVirtualFolder{
		Name:       "ftest rule",
		MappedPath: filepath.Join(os.TempDir(), "p"),
	}
	folder, _, err = httpdtest.AddFolder(folder, http.StatusCreated)
	assert.NoError(t, err)
	// no action is triggered on add
	assert.NoFileExists(t, outPath)
	// update the folder
	_, _, err = httpdtest.UpdateFolder(folder, http.StatusOK)
	assert.NoError(t, err)
	if assert.Eventually(t, func() bool {
		_, err := os.Stat(outPath)
		return err == nil
	}, 2*time.Second, 100*time.Millisecond) {
		content, err := os.ReadFile(outPath)
		assert.NoError(t, err)
		var folderGet vfs.BaseVirtualFolder
		err = json.Unmarshal(content, &folderGet)
		assert.NoError(t, err)
		assert.Equal(t, folder, folderGet)
		err = os.Remove(outPath)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3000*time.Millisecond, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, "test3@example.com"))
		assert.Contains(t, email.Data, `Subject: New "update" from "admin"`)
	}
	// now delete the script to generate an error
	lastReceivedEmail.reset()
	err = os.Remove(saveObjectScriptPath)
	assert.NoError(t, err)
	_, _, err = httpdtest.UpdateFolder(folder, http.StatusOK)
	assert.NoError(t, err)
	assert.NoFileExists(t, outPath)
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email := lastReceivedEmail.get()
	assert.Len(t, email.To, 1)
	assert.True(t, util.Contains(email.To, "failure@example.com"))
	assert.Contains(t, email.Data, `Subject: Failed "update" from "admin"`)
	assert.Contains(t, email.Data, fmt.Sprintf("Object name: %s object type: folder", folder.Name))
	lastReceivedEmail.reset()
	// generate an error for the failure action
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	_, _, err = httpdtest.UpdateFolder(folder, http.StatusOK)
	assert.NoError(t, err)
	assert.NoFileExists(t, outPath)
	email = lastReceivedEmail.get()
	assert.Len(t, email.To, 0)
	_, err = httpdtest.RemoveFolder(folder, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
	assert.NoError(t, err)
}
func TestEventRuleFsActions(t *testing.T) {
	dirsToCreate := []string{
		"/basedir/1",
		"/basedir/sub/2",
		"/basedir/3",
	}
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type:   dataprovider.FilesystemActionMkdirs,
				MkDirs: dirsToCreate,
			},
		},
	}
	a2 := dataprovider.BaseEventAction{
		Name: "a2",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type: dataprovider.FilesystemActionRename,
				Renames: []dataprovider.KeyValue{
					{
						Key:   "/{{VirtualDirPath}}/{{ObjectName}}",
						Value: "/{{ObjectName}}_renamed",
					},
				},
			},
		},
	}
	a3 := dataprovider.BaseEventAction{
		Name: "a3",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type:    dataprovider.FilesystemActionDelete,
				Deletes: []string{"/{{ObjectName}}_renamed"},
			},
		},
	}
	a4 := dataprovider.BaseEventAction{
		Name: "a4",
		Type: dataprovider.ActionTypeFolderQuotaReset,
	}
	a5 := dataprovider.BaseEventAction{
		Name: "a5",
		Type: dataprovider.ActionTypeUserQuotaReset,
	}
	a6 := dataprovider.BaseEventAction{
		Name: "a6",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type:  dataprovider.FilesystemActionExist,
				Exist: []string{"/{{VirtualPath}}"},
			},
		},
	}
	action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	action2, resp, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	action3, resp, err := httpdtest.AddEventAction(a3, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	action4, resp, err := httpdtest.AddEventAction(a4, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	action5, resp, err := httpdtest.AddEventAction(a5, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	action6, resp, err := httpdtest.AddEventAction(a6, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	r1 := dataprovider.EventRule{
		Name:    "r1",
		Status:  1,
		Trigger: dataprovider.EventTriggerProviderEvent,
		Conditions: dataprovider.EventConditions{
			ProviderEvents: []string{"add"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
		},
	}
	r2 := dataprovider.EventRule{
		Name:    "r2",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action5.Name,
				},
				Order: 2,
			},
		},
	}
	r3 := dataprovider.EventRule{
		Name:    "r3",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"mkdir"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action3.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action6.Name,
				},
				Order: 2,
			},
		},
	}
	r4 := dataprovider.EventRule{
		Name:    "r4",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"rmdir"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action4.Name,
				},
				Order: 1,
			},
		},
	}
	r5 := dataprovider.EventRule{
		Name:    "r5",
		Status:  1,
		Trigger: dataprovider.EventTriggerProviderEvent,
		Conditions: dataprovider.EventConditions{
			ProviderEvents: []string{"add"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action4.Name,
				},
				Order: 1,
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
	assert.NoError(t, err)
	rule3, _, err := httpdtest.AddEventRule(r3, http.StatusCreated)
	assert.NoError(t, err)
	rule4, _, err := httpdtest.AddEventRule(r4, http.StatusCreated)
	assert.NoError(t, err)
	rule5, _, err := httpdtest.AddEventRule(r5, http.StatusCreated)
	assert.NoError(t, err)
	folderMappedPath := filepath.Join(os.TempDir(), "folder")
	err = os.MkdirAll(folderMappedPath, os.ModePerm)
	assert.NoError(t, err)
	err = os.WriteFile(filepath.Join(folderMappedPath, "file.txt"), []byte("1"), 0666)
	assert.NoError(t, err)
	folder, _, err := httpdtest.AddFolder(vfs.BaseVirtualFolder{
		Name:       "test folder",
		MappedPath: folderMappedPath,
	}, http.StatusCreated)
	assert.NoError(t, err)
	assert.Eventually(t, func() bool {
		folderGet, _, err := httpdtest.GetFolderByName(folder.Name, http.StatusOK)
		if err != nil {
			return false
		}
		return folderGet.UsedQuotaFiles == 1 && folderGet.UsedQuotaSize == 1
	}, 2*time.Second, 100*time.Millisecond)
	u := getTestUser()
	u.Filters.DisableFsChecks = true
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		// check initial directories creation
		for _, dir := range dirsToCreate {
			assert.Eventually(t, func() bool {
				_, err := client.Stat(dir)
				return err == nil
			}, 2*time.Second, 100*time.Millisecond)
		}
		// upload a file and check the sync rename
		size := int64(32768)
		err = writeSFTPFileNoCheck(path.Join("basedir", testFileName), size, client)
		assert.NoError(t, err)
		_, err = client.Stat(path.Join("basedir", testFileName))
		assert.Error(t, err)
		info, err := client.Stat(testFileName + "_renamed")
		if assert.NoError(t, err) {
			assert.Equal(t, size, info.Size())
		}
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			userGet, _, err := httpdtest.GetUserByUsername(user.Username, http.StatusOK)
			if err != nil {
				return false
			}
			return userGet.UsedQuotaFiles == 1 && userGet.UsedQuotaSize == size
		}, 2*time.Second, 100*time.Millisecond)
		for i := 0; i < 2; i++ {
			err = client.Mkdir(testFileName)
			assert.NoError(t, err)
			assert.Eventually(t, func() bool {
				_, err = client.Stat(testFileName + "_renamed")
				return err != nil
			}, 2*time.Second, 100*time.Millisecond)
			err = client.RemoveDirectory(testFileName)
			assert.NoError(t, err)
		}
		err = client.Mkdir(testFileName + "_renamed")
		assert.NoError(t, err)
		err = client.Mkdir(testFileName)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			_, err = client.Stat(testFileName + "_renamed")
			return err != nil
		}, 2*time.Second, 100*time.Millisecond)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(folder, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(folderMappedPath)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule4, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule5, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action4, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action5, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action6, http.StatusOK)
	assert.NoError(t, err)
}
func TestEventRulePreDelete(t *testing.T) {
	movePath := "recycle bin"
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type: dataprovider.FilesystemActionRename,
				Renames: []dataprovider.KeyValue{
					{
						Key:   "/{{VirtualPath}}",
						Value: fmt.Sprintf("/%s/{{VirtualPath}}", movePath),
					},
				},
			},
		},
	}
	action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	r1 := dataprovider.EventRule{
		Name:    "rule1",
		Status:  1,
		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{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	f := vfs.BaseVirtualFolder{
		Name:       movePath,
		MappedPath: filepath.Join(os.TempDir(), movePath),
	}
	_, _, err = httpdtest.AddFolder(f, http.StatusCreated)
	assert.NoError(t, err)
	u := getTestUser()
	u.QuotaFiles = 1000
	u.VirtualFolders = []vfs.VirtualFolder{
		{
			BaseVirtualFolder: vfs.BaseVirtualFolder{
				Name: 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) {
		defer conn.Close()
		defer client.Close()
		testDir := "sub dir"
		err = client.MkdirAll(testDir)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName, 100, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, testFileName), 100, client)
		assert.NoError(t, err)
		err = client.Remove(testFileName)
		assert.NoError(t, err)
		err = client.Remove(path.Join(testDir, testFileName))
		assert.NoError(t, err)
		// check files
		_, err = client.Stat(testFileName)
		assert.ErrorIs(t, err, os.ErrNotExist)
		_, err = client.Stat(path.Join(testDir, testFileName))
		assert.ErrorIs(t, err, os.ErrNotExist)
		_, err = client.Stat(path.Join("/", movePath, testFileName))
		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)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	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) {
	testDir := "/d"
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type:   dataprovider.FilesystemActionMkdirs,
				MkDirs: []string{testDir},
			},
		},
	}
	action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	a2 := dataprovider.BaseEventAction{
		Name: "a2",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type: dataprovider.FilesystemActionRename,
				Renames: []dataprovider.KeyValue{
					{
						Key:   "/missing source",
						Value: "/missing target",
					},
				},
			},
		},
	}
	action2, resp, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	r1 := dataprovider.EventRule{
		Name:    "rule1",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"pre-download", "pre-upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		// the rule will always succeed, so uploads/downloads will work
		err = writeSFTPFile(testFileName, 100, client)
		assert.NoError(t, err)
		_, err = client.Stat(testDir)
		assert.NoError(t, err)
		err = client.RemoveDirectory(testDir)
		assert.NoError(t, err)
		f, err := client.Open(testFileName)
		assert.NoError(t, err)
		contents := make([]byte, 100)
		n, err := io.ReadFull(f, contents)
		assert.NoError(t, err)
		assert.Equal(t, int(100), n)
		err = f.Close()
		assert.NoError(t, err)
		// disable the rule
		rule1.Status = 0
		_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
		assert.NoError(t, err)
		err = client.RemoveDirectory(testDir)
		assert.NoError(t, err)
		err = client.Remove(testFileName)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName, 100, client)
		assert.NoError(t, err)
		_, err = client.Stat(testDir)
		assert.ErrorIs(t, err, fs.ErrNotExist)
		// now update the rule so that it will always fail
		rule1.Status = 1
		rule1.Actions = []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
		}
		_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
		assert.NoError(t, err)
		_, err = client.Open(testFileName)
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Remove(testFileName)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName, 100, client)
		assert.ErrorIs(t, err, os.ErrPermission)
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestFsActionCopy(t *testing.T) {
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type: dataprovider.FilesystemActionCopy,
				Copy: []dataprovider.KeyValue{
					{
						Key:   "/{{VirtualPath}}/",
						Value: "/dircopy/",
					},
				},
			},
		},
	}
	action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	r1 := dataprovider.EventRule{
		Name:    "rule1",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = writeSFTPFile(testFileName, 100, client)
		assert.NoError(t, err)
		_, err = client.Stat(path.Join("dircopy", testFileName))
		assert.NoError(t, err)
		action1.Options.FsConfig.Copy = []dataprovider.KeyValue{
			{
				Key:   "/missing path",
				Value: "/copied path",
			},
		}
		_, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
		assert.NoError(t, err)
		// copy a missing path will fail
		err = writeSFTPFile(testFileName, 100, client)
		assert.Error(t, err)
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestEventFsActionsGroupFilters(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notification@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"example@example.net"},
				Subject:    `New "{{Event}}" from "{{Name}}" status {{StatusString}}`,
				Body:       "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} {{ErrorString}}",
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "rule1",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
			Options: dataprovider.ConditionOptions{
				GroupNames: []dataprovider.ConditionPattern{
					{
						Pattern: "group*",
					},
				},
			},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		// the user has no group, so the rule does not match
		lastReceivedEmail.reset()
		err = writeSFTPFile(testFileName, 32, client)
		assert.NoError(t, err)
		assert.Empty(t, lastReceivedEmail.get().From)
	}
	g1 := dataprovider.Group{
		BaseGroup: sdk.BaseGroup{
			Name: "agroup1",
		},
	}
	group1, _, err := httpdtest.AddGroup(g1, http.StatusCreated)
	assert.NoError(t, err)
	g2 := dataprovider.Group{
		BaseGroup: sdk.BaseGroup{
			Name: "group2",
		},
	}
	group2, _, err := httpdtest.AddGroup(g2, http.StatusCreated)
	assert.NoError(t, err)
	user.Groups = []sdk.GroupMapping{
		{
			Name: group1.Name,
			Type: sdk.GroupTypePrimary,
		},
	}
	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		// the group does not match
		lastReceivedEmail.reset()
		err = writeSFTPFile(testFileName, 32, client)
		assert.NoError(t, err)
		assert.Empty(t, lastReceivedEmail.get().From)
	}
	user.Groups = append(user.Groups, sdk.GroupMapping{
		Name: group2.Name,
		Type: sdk.GroupTypeSecondary,
	})
	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		// the group matches
		lastReceivedEmail.reset()
		err = writeSFTPFile(testFileName, 32, client)
		assert.NoError(t, err)
		assert.NotEmpty(t, lastReceivedEmail.get().From)
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveGroup(group1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveGroup(group2, http.StatusOK)
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestBackupAsAttachment(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notification@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeBackup,
	}
	a2 := dataprovider.BaseEventAction{
		Name: "a2",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients:  []string{"test@example.com"},
				Subject:     `"{{Event}} {{StatusString}}"`,
				Body:        "Domain: {{Name}}",
				Attachments: []string{"/{{VirtualPath}}"},
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test rule certificate",
		Status:  1,
		Trigger: dataprovider.EventTriggerCertificate,
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 2,
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	lastReceivedEmail.reset()
	renewalEvent := "Certificate renewal"
	common.HandleCertificateEvent(common.EventParams{
		Name:      "example.com",
		Timestamp: time.Now().UnixNano(),
		Status:    1,
		Event:     renewalEvent,
	})
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email := lastReceivedEmail.get()
	assert.Len(t, email.To, 1)
	assert.True(t, util.Contains(email.To, "test@example.com"))
	assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
	assert.Contains(t, email.Data, `Domain: example.com`)
	assert.Contains(t, email.Data, "Content-Type: application/json")
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventActionHTTPMultipart(t *testing.T) {
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeHTTP,
		Options: dataprovider.BaseEventActionOptions{
			HTTPConfig: dataprovider.EventActionHTTPConfig{
				Endpoint: fmt.Sprintf("http://%s/multipart", httpAddr),
				Method:   http.MethodPut,
				Parts: []dataprovider.HTTPPart{
					{
						Name: "part1",
						Headers: []dataprovider.KeyValue{
							{
								Key:   "Content-Type",
								Value: "application/json",
							},
						},
						Body: `{"FilePath": "{{VirtualPath}}"}`,
					},
					{
						Name:     "file",
						Filepath: "/{{VirtualPath}}",
					},
				},
			},
		},
	}
	action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	r1 := dataprovider.EventRule{
		Name:    "test http multipart",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
				Order: 1,
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		f, err := client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		// now add an missing file to the http multipart action
		action1.Options.HTTPConfig.Parts = append(action1.Options.HTTPConfig.Parts, dataprovider.HTTPPart{
			Name:     "file1",
			Filepath: "/missing",
		})
		_, resp, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
		assert.NoError(t, err, string(resp))
		f, err = client.Create("testfile.txt")
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.Error(t, err)
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestEventActionCompress(t *testing.T) {
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type: dataprovider.FilesystemActionCompress,
				Compress: dataprovider.EventActionFsCompress{
					Name:  "/{{VirtualPath}}.zip",
					Paths: []string{"/{{VirtualPath}}"},
				},
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test compress",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	u := getTestUser()
	u.QuotaFiles = 1000
	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	u = getTestSFTPUser()
	u.FsConfig.SFTPConfig.BufferSize = 1
	u.QuotaFiles = 1000
	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	u = getCryptFsUser()
	u.QuotaFiles = 1000
	cryptFsUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	for _, user := range []dataprovider.User{localUser, sftpUser, cryptFsUser} {
		// cleanup home dir
		err = os.RemoveAll(user.GetHomeDir())
		assert.NoError(t, err)
		rule1.Conditions.Options.Names = []dataprovider.ConditionPattern{
			{
				Pattern: user.Username,
			},
		}
		_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
		assert.NoError(t, err)
		conn, client, err := getSftpClient(user)
		if assert.NoError(t, err) {
			defer conn.Close()
			defer client.Close()
			expectedQuotaSize := int64(len(testFileContent))
			expectedQuotaFiles := 1
			if user.Username == cryptFsUser.Username {
				encryptedFileSize, err := getEncryptedFileSize(expectedQuotaSize)
				assert.NoError(t, err)
				expectedQuotaSize = encryptedFileSize
			}
			f, err := client.Create(testFileName)
			assert.NoError(t, err)
			_, err = f.Write(testFileContent)
			assert.NoError(t, err)
			err = f.Close()
			assert.NoError(t, err)
			info, err := client.Stat(testFileName + ".zip")
			if assert.NoError(t, err) {
				assert.Greater(t, info.Size(), int64(0))
				// check quota
				archiveSize := info.Size()
				if user.Username == cryptFsUser.Username {
					encryptedFileSize, err := getEncryptedFileSize(archiveSize)
					assert.NoError(t, err)
					archiveSize = encryptedFileSize
				}
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, expectedQuotaFiles+1, user.UsedQuotaFiles,
					"quota file does no match for user %q", user.Username)
				assert.Equal(t, expectedQuotaSize+archiveSize, user.UsedQuotaSize,
					"quota size does no match for user %q", user.Username)
			}
			// now overwrite the same file
			f, err = client.Create(testFileName)
			assert.NoError(t, err)
			_, err = f.Write(testFileContent)
			assert.NoError(t, err)
			err = f.Close()
			assert.NoError(t, err)
			info, err = client.Stat(testFileName + ".zip")
			if assert.NoError(t, err) {
				assert.Greater(t, info.Size(), int64(0))
				archiveSize := info.Size()
				if user.Username == cryptFsUser.Username {
					encryptedFileSize, err := getEncryptedFileSize(archiveSize)
					assert.NoError(t, err)
					archiveSize = encryptedFileSize
				}
				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
				assert.NoError(t, err)
				assert.Equal(t, expectedQuotaFiles+1, user.UsedQuotaFiles,
					"quota file after overwrite does no match for user %q", user.Username)
				assert.Equal(t, expectedQuotaSize+archiveSize, user.UsedQuotaSize,
					"quota size after overwrite does no match for user %q", user.Username)
			}
		}
		if user.Username == localUser.Username {
			err = os.RemoveAll(user.GetHomeDir())
			assert.NoError(t, err)
		}
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(localUser.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(cryptFsUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(cryptFsUser.GetHomeDir())
	assert.NoError(t, err)
}
func TestEventActionCompressQuotaErrors(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notify@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	testDir := "archiveDir"
	zipPath := "/archive.zip"
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type: dataprovider.FilesystemActionCompress,
				Compress: dataprovider.EventActionFsCompress{
					Name:  zipPath,
					Paths: []string{"/" + testDir},
				},
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	a2 := dataprovider.BaseEventAction{
		Name: "action2",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"test@example.com"},
				Subject:    `"Compress failed"`,
				Body:       "Error: {{ErrorString}}",
			},
		},
	}
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test compress",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"rename"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Options: dataprovider.EventActionOptions{
					IsFailureAction: true,
				},
				Order: 2,
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	fileSize := int64(100)
	u := getTestUser()
	u.QuotaSize = 10 * fileSize
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.MkdirAll(path.Join(testDir, "1", "1"))
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, "1", testFileName), fileSize, client)
		assert.NoError(t, err)
		err = client.MkdirAll(path.Join(testDir, "2", "2"))
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, "2", testFileName), fileSize, client)
		assert.NoError(t, err)
		err = client.Symlink(path.Join(testDir, "2", testFileName), path.Join(testDir, "2", testFileName+"_link"))
		assert.NoError(t, err)
		// trigger the compress action
		err = client.Mkdir("a")
		assert.NoError(t, err)
		err = client.Rename("a", "b")
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			_, err := client.Stat(zipPath)
			return err == nil
		}, 3*time.Second, 100*time.Millisecond)
		err = client.Remove(zipPath)
		assert.NoError(t, err)
		// add other 6 file, the compress action should fail with a quota error
		err = writeSFTPFile(path.Join(testDir, "1", "1", testFileName), fileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, "2", "2", testFileName), fileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, "1", "1", testFileName+"1"), fileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, "2", "2", testFileName+"2"), fileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, "1", testFileName+"1"), fileSize, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, "2", testFileName+"2"), fileSize, client)
		assert.NoError(t, err)
		lastReceivedEmail.reset()
		err = client.Rename("b", "a")
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3*time.Second, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, "test@example.com"))
		assert.Contains(t, email.Data, `Subject: "Compress failed"`)
		assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
		// update quota size so the user is already overquota
		user.QuotaSize = 7 * fileSize
		_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
		assert.NoError(t, err)
		lastReceivedEmail.reset()
		err = client.Rename("a", "b")
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3*time.Second, 100*time.Millisecond)
		email = lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, "test@example.com"))
		assert.Contains(t, email.Data, `Subject: "Compress failed"`)
		assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
		// remove the path to compress to trigger an error for size estimation
		out, err := runSSHCommand(fmt.Sprintf("sftpgo-remove %s", testDir), user)
		assert.NoError(t, err, string(out))
		lastReceivedEmail.reset()
		err = client.Rename("b", "a")
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3*time.Second, 100*time.Millisecond)
		email = lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, "test@example.com"))
		assert.Contains(t, email.Data, `Subject: "Compress failed"`)
		assert.Contains(t, email.Data, "unable to estimate archive size")
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventActionCompressQuotaFolder(t *testing.T) {
	testDir := "/folder"
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type: dataprovider.FilesystemActionCompress,
				Compress: dataprovider.EventActionFsCompress{
					Name:  "/{{VirtualPath}}.zip",
					Paths: []string{"/{{VirtualPath}}", testDir},
				},
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test compress",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	u := getTestUser()
	u.QuotaFiles = 1000
	mappedPath := filepath.Join(os.TempDir(), "virtualpath")
	folderName := filepath.Base(mappedPath)
	vdirPath := "/virtualpath"
	f := vfs.BaseVirtualFolder{
		Name:       folderName,
		MappedPath: mappedPath,
	}
	_, _, err = httpdtest.AddFolder(f, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName,
		},
		VirtualPath: vdirPath,
		QuotaSize:   -1,
		QuotaFiles:  -1,
	})
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		expectedQuotaSize := int64(len(testFileContent))
		expectedQuotaFiles := 1
		err = client.Symlink(path.Join(testDir, testFileName), path.Join(testDir, testFileName+"_link"))
		assert.NoError(t, err)
		f, err := client.Create(path.Join(testDir, testFileName))
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		info, err := client.Stat(path.Join(testDir, testFileName) + ".zip")
		if assert.NoError(t, err) {
			assert.Greater(t, info.Size(), int64(0))
			user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
			assert.NoError(t, err)
			expectedQuotaFiles++
			expectedQuotaSize += info.Size()
			assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
			assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
		}
		vfolder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 0, vfolder.UsedQuotaFiles)
		assert.Equal(t, int64(0), vfolder.UsedQuotaSize)
		// upload in the virtual path
		f, err = client.Create(path.Join(vdirPath, testFileName))
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		info, err = client.Stat(path.Join(vdirPath, testFileName) + ".zip")
		if assert.NoError(t, err) {
			assert.Greater(t, info.Size(), int64(0))
			user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
			assert.NoError(t, err)
			expectedQuotaFiles += 2
			expectedQuotaSize += info.Size() + int64(len(testFileContent))
			assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
			assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
			vfolder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
			assert.NoError(t, err)
			assert.Equal(t, 2, vfolder.UsedQuotaFiles)
			assert.Equal(t, info.Size()+int64(len(testFileContent)), vfolder.UsedQuotaSize)
		}
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath)
	assert.NoError(t, err)
}
func TestEventActionCompressErrors(t *testing.T) {
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type: dataprovider.FilesystemActionCompress,
				Compress: dataprovider.EventActionFsCompress{
					Name:  "/{{VirtualPath}}.zip",
					Paths: []string{"/{{VirtualPath}}.zip"}, // cannot compress itself
				},
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test compress",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		f, err := client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.Error(t, err)
	}
	// try to compress a missing file
	action1.Options.FsConfig.Compress.Paths = []string{"/missing file"}
	_, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		f, err := client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.Error(t, err)
	}
	// try to overwrite a directory
	testDir := "/adir"
	action1.Options.FsConfig.Compress.Name = testDir
	action1.Options.FsConfig.Compress.Paths = []string{"/{{VirtualPath}}"}
	_, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		f, err := client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.Error(t, err)
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestEventActionEmailAttachments(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notify@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeFilesystem,
		Options: dataprovider.BaseEventActionOptions{
			FsConfig: dataprovider.EventActionFilesystemConfig{
				Type: dataprovider.FilesystemActionCompress,
				Compress: dataprovider.EventActionFsCompress{
					Name:  "/archive/{{VirtualPath}}.zip",
					Paths: []string{"/{{VirtualPath}}"},
				},
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	a2 := dataprovider.BaseEventAction{
		Name: "action2",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients:  []string{"test@example.com"},
				Subject:     `"{{Event}}" from "{{Name}}"`,
				Body:        "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}}",
				Attachments: []string{"/archive/{{VirtualPath}}.zip"},
			},
		},
	}
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test email with attachment",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 2,
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	localUser, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	u := getTestSFTPUser()
	u.FsConfig.SFTPConfig.BufferSize = 1
	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	cryptFsUser, _, err := httpdtest.AddUser(getCryptFsUser(), http.StatusCreated)
	assert.NoError(t, err)
	for _, user := range []dataprovider.User{localUser, sftpUser, cryptFsUser} {
		conn, client, err := getSftpClient(user)
		if assert.NoError(t, err) {
			defer conn.Close()
			defer client.Close()
			lastReceivedEmail.reset()
			f, err := client.Create(testFileName)
			assert.NoError(t, err)
			_, err = f.Write(testFileContent)
			assert.NoError(t, err)
			err = f.Close()
			assert.NoError(t, err)
			assert.Eventually(t, func() bool {
				return lastReceivedEmail.get().From != ""
			}, 1500*time.Millisecond, 100*time.Millisecond)
			email := lastReceivedEmail.get()
			assert.Len(t, email.To, 1)
			assert.True(t, util.Contains(email.To, "test@example.com"))
			assert.Contains(t, email.Data, `Subject: "upload" from`)
			assert.Contains(t, email.Data, "Content-Disposition: attachment")
		}
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(localUser.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(cryptFsUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(cryptFsUser.GetHomeDir())
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventActionsRetentionReports(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notify@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	testDir := "/d"
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeDataRetentionCheck,
		Options: dataprovider.BaseEventActionOptions{
			RetentionConfig: dataprovider.EventActionDataRetentionConfig{
				Folders: []dataprovider.FolderRetention{
					{
						Path:                  testDir,
						Retention:             1,
						DeleteEmptyDirs:       true,
						IgnoreUserPermissions: true,
					},
				},
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	a2 := dataprovider.BaseEventAction{
		Name: "action2",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients:  []string{"test@example.com"},
				Subject:     `"{{Event}}" from "{{Name}}"`,
				Body:        "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}}",
				Attachments: []string{dataprovider.RetentionReportPlaceHolder},
			},
		},
	}
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	a3 := dataprovider.BaseEventAction{
		Name: "action3",
		Type: dataprovider.ActionTypeHTTP,
		Options: dataprovider.BaseEventActionOptions{
			HTTPConfig: dataprovider.EventActionHTTPConfig{
				Endpoint: fmt.Sprintf("http://%s/", httpAddr),
				Timeout:  20,
				Method:   http.MethodPost,
				Body:     dataprovider.RetentionReportPlaceHolder,
			},
		},
	}
	action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
	assert.NoError(t, err)
	a4 := dataprovider.BaseEventAction{
		Name: "action4",
		Type: dataprovider.ActionTypeHTTP,
		Options: dataprovider.BaseEventActionOptions{
			HTTPConfig: dataprovider.EventActionHTTPConfig{
				Endpoint: fmt.Sprintf("http://%s/multipart", httpAddr),
				Timeout:  20,
				Method:   http.MethodPost,
				Parts: []dataprovider.HTTPPart{
					{
						Name:     "reports.zip",
						Filepath: dataprovider.RetentionReportPlaceHolder,
					},
				},
			},
		},
	}
	action4, _, err := httpdtest.AddEventAction(a4, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test rule1",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync:   true,
					StopOnFailure: true,
				},
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 2,
				Options: dataprovider.EventActionOptions{
					ExecuteSync:   true,
					StopOnFailure: true,
				},
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action3.Name,
				},
				Order: 3,
				Options: dataprovider.EventActionOptions{
					ExecuteSync:   true,
					StopOnFailure: true,
				},
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action4.Name,
				},
				Order: 4,
				Options: dataprovider.EventActionOptions{
					ExecuteSync:   true,
					StopOnFailure: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		subdir := path.Join(testDir, "sub")
		err = client.MkdirAll(subdir)
		assert.NoError(t, err)
		lastReceivedEmail.reset()
		f, err := client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, "test@example.com"))
		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "upload" from "%s"`, user.Username))
		assert.Contains(t, email.Data, "Content-Disposition: attachment")
		_, err = client.Stat(testDir)
		assert.NoError(t, err)
		_, err = client.Stat(subdir)
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = client.Mkdir(subdir)
		assert.NoError(t, err)
		newName := path.Join(testDir, testFileName)
		err = client.Rename(testFileName, newName)
		assert.NoError(t, err)
		err = client.Chtimes(newName, time.Now().Add(-24*time.Hour), time.Now().Add(-24*time.Hour))
		assert.NoError(t, err)
		lastReceivedEmail.reset()
		f, err = client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.NoError(t, err)
		email = lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		_, err = client.Stat(subdir)
		assert.ErrorIs(t, err, os.ErrNotExist)
		_, err = client.Stat(subdir)
		assert.ErrorIs(t, err, os.ErrNotExist)
	}
	// now remove the retention check to test errors
	rule1.Actions = []dataprovider.EventAction{
		{
			BaseEventAction: dataprovider.BaseEventAction{
				Name: action2.Name,
			},
			Order: 2,
			Options: dataprovider.EventActionOptions{
				ExecuteSync:   true,
				StopOnFailure: false,
			},
		},
		{
			BaseEventAction: dataprovider.BaseEventAction{
				Name: action3.Name,
			},
			Order: 3,
			Options: dataprovider.EventActionOptions{
				ExecuteSync:   true,
				StopOnFailure: false,
			},
		},
		{
			BaseEventAction: dataprovider.BaseEventAction{
				Name: action4.Name,
			},
			Order: 4,
			Options: dataprovider.EventActionOptions{
				ExecuteSync:   true,
				StopOnFailure: false,
			},
		},
	}
	_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		f, err := client.Create(testFileName)
		assert.NoError(t, err)
		_, err = f.Write(testFileContent)
		assert.NoError(t, err)
		err = f.Close()
		assert.Error(t, err)
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action4, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notify@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"test@example.com"},
				Subject:    `"{{Event}}" from "{{Name}}"`,
				Body:       "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}}",
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test first upload rule",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"first-upload"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	r2 := dataprovider.EventRule{
		Name:    "test first download rule",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"first-download"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
		},
	}
	rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
	assert.NoError(t, err)
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileSize := int64(32768)
		lastReceivedEmail.reset()
		err = writeSFTPFileNoCheck(testFileName, testFileSize, client)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 1500*time.Millisecond, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, "test@example.com"))
		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-upload" from "%s"`, user.Username))
		lastReceivedEmail.reset()
		// a new upload will not produce a new notification
		err = writeSFTPFileNoCheck(testFileName+"_1", 32768, client)
		assert.NoError(t, err)
		assert.Never(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 1000*time.Millisecond, 100*time.Millisecond)
		// the same for download
		f, err := client.Open(testFileName)
		assert.NoError(t, err)
		contents := make([]byte, testFileSize)
		n, err := io.ReadFull(f, contents)
		assert.NoError(t, err)
		assert.Equal(t, int(testFileSize), n)
		err = f.Close()
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 1500*time.Millisecond, 100*time.Millisecond)
		email = lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, "test@example.com"))
		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-download" from "%s"`, user.Username))
		// download again
		lastReceivedEmail.reset()
		f, err = client.Open(testFileName)
		assert.NoError(t, err)
		contents = make([]byte, testFileSize)
		n, err = io.ReadFull(f, contents)
		assert.NoError(t, err)
		assert.Equal(t, int(testFileSize), n)
		err = f.Close()
		assert.NoError(t, err)
		assert.Never(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 1000*time.Millisecond, 100*time.Millisecond)
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventRuleRenameEvent(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notify@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients:  []string{"test@example.com"},
				Subject:     `"{{Event}}" from "{{Name}}"`,
				ContentType: 1,
				Body:        `
Fs path {{FsPath}}, Target path "{{VirtualTargetDirPath}}/{{TargetName}}", size: {{FileSize}}
`,
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test rename rule",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"rename"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		testFileSize := int64(32768)
		lastReceivedEmail.reset()
		err = writeSFTPFileNoCheck(testFileName, testFileSize, client)
		assert.NoError(t, err)
		err = client.Mkdir("subdir")
		assert.NoError(t, err)
		err = client.Rename(testFileName, path.Join("/subdir", testFileName))
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 1500*time.Millisecond, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, "test@example.com"))
		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "rename" from "%s"`, user.Username))
		assert.Contains(t, email.Data, "Content-Type: text/html")
		assert.Contains(t, email.Data, fmt.Sprintf("Target path %q", path.Join("/subdir", testFileName)))
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventRuleIDPLogin(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notify@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	lastReceivedEmail.reset()
	username := `test_"idp_"login`
	custom1 := `cust"oa"1`
	u := map[string]any{
		"username": "{{Name}}",
		"status":   1,
		"home_dir": filepath.Join(os.TempDir(), "{{IDPFieldcustom1}}"),
		"permissions": map[string][]string{
			"/": {dataprovider.PermAny},
		},
	}
	userTmpl, err := json.Marshal(u)
	require.NoError(t, err)
	a := map[string]any{
		"username":    "{{Name}}",
		"status":      1,
		"permissions": []string{dataprovider.PermAdminAny},
	}
	adminTmpl, err := json.Marshal(a)
	require.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeIDPAccountCheck,
		Options: dataprovider.BaseEventActionOptions{
			IDPConfig: dataprovider.EventActionIDPAccountCheck{
				Mode:          1, // create if not exists
				TemplateUser:  string(userTmpl),
				TemplateAdmin: string(adminTmpl),
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	a2 := dataprovider.BaseEventAction{
		Name: "a2",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"test@example.com"},
				Subject:    `"{{Event}} {{StatusString}}"`,
				Body:       "{{Name}} Custom field: {{IDPFieldcustom1}}",
			},
		},
	}
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test rule IDP login",
		Status:  1,
		Trigger: dataprovider.EventTriggerIDPLogin,
		Conditions: dataprovider.EventConditions{
			IDPLoginEvent: dataprovider.IDPLoginUser,
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name, // the rule is not sync and will be skipped
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 2,
			},
		},
	}
	rule1, resp, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	customFields := map[string]any{
		"custom1": custom1,
	}
	user, admin, err := common.HandleIDPLoginEvent(common.EventParams{
		Name:   username,
		Event:  common.IDPLoginUser,
		Status: 1,
	}, &customFields)
	assert.Nil(t, user)
	assert.Nil(t, admin)
	assert.NoError(t, err)
	rule1.Actions[0].Options.ExecuteSync = true
	rule1, resp, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
	assert.NoError(t, err, string(resp))
	user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
		Name:   username,
		Event:  common.IDPLoginUser,
		Status: 1,
	}, &customFields)
	if assert.NotNil(t, user) {
		assert.Equal(t, filepath.Join(os.TempDir(), custom1), user.GetHomeDir())
		_, err = httpdtest.RemoveUser(*user, http.StatusOK)
		assert.NoError(t, err)
	}
	assert.Nil(t, admin)
	assert.NoError(t, err)
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email := lastReceivedEmail.get()
	assert.Len(t, email.To, 1)
	assert.True(t, util.Contains(email.To, "test@example.com"))
	assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginUser))
	assert.Contains(t, email.Data, username)
	assert.Contains(t, email.Data, custom1)
	user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
		Name:   username,
		Event:  common.IDPLoginAdmin,
		Status: 1,
	}, &customFields)
	assert.Nil(t, user)
	assert.Nil(t, admin)
	assert.NoError(t, err)
	rule1.Conditions.IDPLoginEvent = dataprovider.IDPLoginAny
	rule1.Actions = []dataprovider.EventAction{
		{
			BaseEventAction: dataprovider.BaseEventAction{
				Name: action1.Name,
			},
			Options: dataprovider.EventActionOptions{
				ExecuteSync: true,
			},
			Order: 1,
		},
	}
	rule1, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	r2 := dataprovider.EventRule{
		Name:    "test email on IDP login",
		Status:  1,
		Trigger: dataprovider.EventTriggerIDPLogin,
		Conditions: dataprovider.EventConditions{
			IDPLoginEvent: dataprovider.IDPLoginAdmin,
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 1,
			},
		},
	}
	rule2, resp, err := httpdtest.AddEventRule(r2, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	lastReceivedEmail.reset()
	user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
		Name:   username,
		Event:  common.IDPLoginAdmin,
		Status: 1,
	}, &customFields)
	assert.Nil(t, user)
	if assert.NotNil(t, admin) {
		assert.Equal(t, 1, admin.Status)
	}
	assert.NoError(t, err)
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email = lastReceivedEmail.get()
	assert.Len(t, email.To, 1)
	assert.True(t, util.Contains(email.To, "test@example.com"))
	assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginAdmin))
	assert.Contains(t, email.Data, username)
	assert.Contains(t, email.Data, custom1)
	admin.Status = 0
	_, _, err = httpdtest.UpdateAdmin(*admin, http.StatusOK)
	assert.NoError(t, err)
	user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
		Name:   username,
		Event:  common.IDPLoginAdmin,
		Status: 1,
	}, &customFields)
	assert.Nil(t, user)
	if assert.NotNil(t, admin) {
		assert.Equal(t, 0, admin.Status)
	}
	assert.NoError(t, err)
	action1.Options.IDPConfig.Mode = 0
	action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
		Name:   username,
		Event:  common.IDPLoginAdmin,
		Status: 1,
	}, &customFields)
	assert.Nil(t, user)
	if assert.NotNil(t, admin) {
		assert.Equal(t, 1, admin.Status)
	}
	assert.NoError(t, err)
	_, err = httpdtest.RemoveAdmin(*admin, http.StatusOK)
	assert.NoError(t, err)
	r3 := dataprovider.EventRule{
		Name:    "test rule2 IDP login",
		Status:  1,
		Trigger: dataprovider.EventTriggerIDPLogin,
		Conditions: dataprovider.EventConditions{
			IDPLoginEvent: dataprovider.IDPLoginAny,
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
				Options: dataprovider.EventActionOptions{
					ExecuteSync: true,
				},
			},
		},
	}
	rule3, resp, err := httpdtest.AddEventRule(r3, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
		Name:   username,
		Event:  common.IDPLoginAdmin,
		Status: 1,
	}, &customFields)
	assert.Nil(t, user)
	assert.Nil(t, admin)
	if assert.Error(t, err) {
		assert.Contains(t, err.Error(), "more than one account check action rules matches")
	}
	_, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
	assert.NoError(t, err)
	action1.Options.IDPConfig.TemplateAdmin = `{}`
	action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, _, err = common.HandleIDPLoginEvent(common.EventParams{
		Name:   username,
		Event:  common.IDPLoginAdmin,
		Status: 1,
	}, &customFields)
	assert.ErrorIs(t, err, util.ErrValidation)
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
		Name:   username,
		Event:  common.IDPLoginAdmin,
		Status: 1,
	}, &customFields)
	assert.Nil(t, user)
	assert.Nil(t, admin)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventRuleEmailField(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notify@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	lastReceivedEmail.reset()
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"{{Email}}"},
				Subject:    `"{{Event}}" from "{{Name}}"`,
				Body:       "Sample email body",
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	a2 := dataprovider.BaseEventAction{
		Name: "action2",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"failure@example.com"},
				Subject:    `"Failure`,
				Body:       "{{ErrorString}}",
			},
		},
	}
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "r1",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"mkdir"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
			},
		},
	}
	r2 := dataprovider.EventRule{
		Name:    "test rule2",
		Status:  1,
		Trigger: dataprovider.EventTriggerProviderEvent,
		Conditions: dataprovider.EventConditions{
			ProviderEvents: []string{"add"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Options: dataprovider.EventActionOptions{
					IsFailureAction: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
	assert.NoError(t, err)
	u := getTestUser()
	u.Email = "user@example.com"
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email := lastReceivedEmail.get()
	assert.Len(t, email.To, 1)
	assert.True(t, util.Contains(email.To, user.Email))
	assert.Contains(t, email.Data, `Subject: "add" from "admin"`)
	// if we add a user without email the notification will fail
	lastReceivedEmail.reset()
	u1 := getTestUser()
	u1.Username += "_1"
	user1, _, err := httpdtest.AddUser(u1, http.StatusCreated)
	assert.NoError(t, err)
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email = lastReceivedEmail.get()
	assert.Len(t, email.To, 1)
	assert.True(t, util.Contains(email.To, "failure@example.com"))
	assert.Contains(t, email.Data, `no recipient addresses set`)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		lastReceivedEmail.reset()
		err = client.Mkdir(testFileName)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 3000*time.Millisecond, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.True(t, util.Contains(email.To, user.Email))
		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "mkdir" from "%s"`, user.Username))
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user1, http.StatusOK)
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventRuleCertificate(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notify@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	lastReceivedEmail.reset()
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients:  []string{"test@example.com"},
				Subject:     `"{{Event}} {{StatusString}}"`,
				ContentType: 0,
				Body:        "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}}",
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	a2 := dataprovider.BaseEventAction{
		Name: "action2",
		Type: dataprovider.ActionTypeFolderQuotaReset,
	}
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test rule certificate",
		Status:  1,
		Trigger: dataprovider.EventTriggerCertificate,
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	r2 := dataprovider.EventRule{
		Name:    "test rule 2",
		Status:  1,
		Trigger: dataprovider.EventTriggerCertificate,
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 2,
			},
		},
	}
	rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
	assert.NoError(t, err)
	renewalEvent := "Certificate renewal"
	common.HandleCertificateEvent(common.EventParams{
		Name:      "example.com",
		Timestamp: time.Now().UnixNano(),
		Status:    1,
		Event:     renewalEvent,
	})
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email := lastReceivedEmail.get()
	assert.Len(t, email.To, 1)
	assert.True(t, util.Contains(email.To, "test@example.com"))
	assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
	assert.Contains(t, email.Data, "Content-Type: text/plain")
	assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
	lastReceivedEmail.reset()
	params := common.EventParams{
		Name:      "example.com",
		Timestamp: time.Now().UnixNano(),
		Status:    2,
		Event:     renewalEvent,
	}
	errRenew := errors.New("generic renew error")
	params.AddError(errRenew)
	common.HandleCertificateEvent(params)
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email = lastReceivedEmail.get()
	assert.Len(t, email.To, 1)
	assert.True(t, util.Contains(email.To, "test@example.com"))
	assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
	assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
	assert.Contains(t, email.Data, errRenew.Error())
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	// ignored no more certificate rules
	common.HandleCertificateEvent(common.EventParams{
		Name:      "example.com",
		Timestamp: time.Now().UnixNano(),
		Status:    1,
		Event:     renewalEvent,
	})
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestEventRuleIPBlocked(t *testing.T) {
	oldConfig := config.GetCommonConfig()
	cfg := config.GetCommonConfig()
	cfg.DefenderConfig.Enabled = true
	cfg.DefenderConfig.Threshold = 3
	cfg.DefenderConfig.ScoreLimitExceeded = 2
	err := common.Initialize(cfg, 0)
	assert.NoError(t, err)
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notification@example.com",
		TemplatesPath: "templates",
	}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "action1",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"test3@example.com", "test4@example.com"},
				Subject:    `New "{{Event}}"`,
				Body:       "IP: {{IP}} Timestamp: {{Timestamp}}",
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	a2 := dataprovider.BaseEventAction{
		Name: "action2",
		Type: dataprovider.ActionTypeFolderQuotaReset,
	}
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "test rule ip blocked",
		Status:  1,
		Trigger: dataprovider.EventTriggerIPBlocked,
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	r2 := dataprovider.EventRule{
		Name:    "test rule 2",
		Status:  1,
		Trigger: dataprovider.EventTriggerIPBlocked,
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 2,
			},
		},
	}
	rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
	assert.NoError(t, err)
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	lastReceivedEmail.reset()
	time.Sleep(300 * time.Millisecond)
	assert.Empty(t, lastReceivedEmail.get().From, lastReceivedEmail.get().Data)
	for i := 0; i < 3; i++ {
		user.Password = "wrong_pwd"
		_, _, err = getSftpClient(user)
		assert.Error(t, err)
	}
	// the client is now banned
	user.Password = defaultPassword
	_, _, err = getSftpClient(user)
	assert.Error(t, err)
	// check the email notification
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email := lastReceivedEmail.get()
	assert.Len(t, email.To, 2)
	assert.True(t, util.Contains(email.To, "test3@example.com"))
	assert.True(t, util.Contains(email.To, "test4@example.com"))
	assert.Contains(t, email.Data, `Subject: New "IP Blocked"`)
	err = dataprovider.DeleteEventRule(rule1.Name, "", "", "")
	assert.NoError(t, err)
	err = dataprovider.DeleteEventRule(rule2.Name, "", "", "")
	assert.NoError(t, err)
	err = dataprovider.DeleteEventAction(action1.Name, "", "", "")
	assert.NoError(t, err)
	err = dataprovider.DeleteEventAction(action2.Name, "", "", "")
	assert.NoError(t, err)
	err = dataprovider.DeleteUser(user.Username, "", "", "")
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	err = common.Initialize(oldConfig, 0)
	assert.NoError(t, err)
}
func TestEventRulePasswordExpiration(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notification@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"failure@example.net"},
				Subject:    `Failure`,
				Body:       "Failure action",
			},
		},
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	a2 := dataprovider.BaseEventAction{
		Name: "a2",
		Type: dataprovider.ActionTypePasswordExpirationCheck,
		Options: dataprovider.BaseEventActionOptions{
			PwdExpirationConfig: dataprovider.EventActionPasswordExpiration{
				Threshold: 10,
			},
		},
	}
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	a3 := dataprovider.BaseEventAction{
		Name: "a3",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"success@example.net"},
				Subject:    `OK`,
				Body:       "OK action",
			},
		},
	}
	action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "rule1",
		Status:  1,
		Trigger: dataprovider.EventTriggerFsEvent,
		Conditions: dataprovider.EventConditions{
			FsEvents: []string{"mkdir"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action3.Name,
				},
				Order: 2,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Options: dataprovider.EventActionOptions{
					IsFailureAction: true,
				},
			},
		},
	}
	rule1, resp, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	dirName := "aTestDir"
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		lastReceivedEmail.reset()
		err := client.Mkdir(dirName)
		assert.NoError(t, err)
		// the user has no password expiration, the check will be skipped and the ok action executed
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 1500*time.Millisecond, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.Contains(t, email.To, "success@example.net")
		err = client.RemoveDirectory(dirName)
		assert.NoError(t, err)
	}
	user.Filters.PasswordExpiration = 20
	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		lastReceivedEmail.reset()
		err := client.Mkdir(dirName)
		assert.NoError(t, err)
		// the passowrd is not about to expire, the check will be skipped and the ok action executed
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 1500*time.Millisecond, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.Contains(t, email.To, "success@example.net")
		err = client.RemoveDirectory(dirName)
		assert.NoError(t, err)
	}
	user.Filters.PasswordExpiration = 5
	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		lastReceivedEmail.reset()
		err := client.Mkdir(dirName)
		assert.NoError(t, err)
		// the passowrd is about to expire, the user has no email, the failure action will be executed
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 1500*time.Millisecond, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.Contains(t, email.To, "failure@example.net")
		err = client.RemoveDirectory(dirName)
		assert.NoError(t, err)
	}
	// remove the success action
	rule1.Actions = []dataprovider.EventAction{
		{
			BaseEventAction: dataprovider.BaseEventAction{
				Name: action2.Name,
			},
			Order: 1,
		},
		{
			BaseEventAction: dataprovider.BaseEventAction{
				Name: action1.Name,
			},
			Options: dataprovider.EventActionOptions{
				IsFailureAction: true,
			},
		},
	}
	_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	user.Email = "user@example.net"
	_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		lastReceivedEmail.reset()
		err := client.Mkdir(dirName)
		assert.NoError(t, err)
		// the passowrd expiration will be notified
		assert.Eventually(t, func() bool {
			return lastReceivedEmail.get().From != ""
		}, 1500*time.Millisecond, 100*time.Millisecond)
		email := lastReceivedEmail.get()
		assert.Len(t, email.To, 1)
		assert.Contains(t, email.To, user.Email)
		assert.Contains(t, email.Data, "your SFTPGo password expires in 5 days")
		err = client.RemoveDirectory(dirName)
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestSyncUploadAction(t *testing.T) {
	if runtime.GOOS == osWindows {
		t.Skip("this test is not available on Windows")
	}
	uploadScriptPath := filepath.Join(os.TempDir(), "upload.sh")
	common.Config.Actions.ExecuteOn = []string{"upload"}
	common.Config.Actions.ExecuteSync = []string{"upload"}
	common.Config.Actions.Hook = uploadScriptPath
	u := getTestUser()
	u.QuotaFiles = 1000
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	movedFileName := "moved.dat"
	movedPath := filepath.Join(user.HomeDir, movedFileName)
	err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, "", 0), 0755)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		size := int64(32768)
		err = writeSFTPFileNoCheck(testFileName, size, client)
		assert.NoError(t, err)
		_, err = client.Stat(testFileName)
		assert.Error(t, err)
		info, err := client.Stat(movedFileName)
		if assert.NoError(t, err) {
			assert.Equal(t, size, info.Size())
		}
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, size, user.UsedQuotaSize)
		// test some hook failure
		// the uploaded file is moved and the hook fails, it will be not removed from the quota
		err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, "", 1), 0755)
		assert.NoError(t, err)
		err = writeSFTPFileNoCheck(testFileName+"_1", size, client)
		assert.Error(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, size*2, user.UsedQuotaSize)
		// the uploaded file is not moved and the hook fails, the uploaded file will be deleted
		// and removed from the quota
		movedPath = filepath.Join(user.HomeDir, "missing dir", movedFileName)
		err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, "", 1), 0755)
		assert.NoError(t, err)
		err = writeSFTPFileNoCheck(testFileName+"_2", size, client)
		assert.Error(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 2, user.UsedQuotaFiles)
		assert.Equal(t, size*2, user.UsedQuotaSize)
		// overwrite an existing file
		_, err = client.Stat(movedFileName)
		assert.NoError(t, err)
		err = writeSFTPFileNoCheck(movedFileName, size, client)
		assert.Error(t, err)
		_, err = client.Stat(movedFileName)
		assert.Error(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		assert.Equal(t, size, user.UsedQuotaSize)
	}
	err = os.Remove(uploadScriptPath)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	common.Config.Actions.ExecuteOn = nil
	common.Config.Actions.ExecuteSync = nil
	common.Config.Actions.Hook = uploadScriptPath
}
func TestQuotaTrackDisabled(t *testing.T) {
	err := dataprovider.Close()
	assert.NoError(t, err)
	err = config.LoadConfig(configDir, "")
	assert.NoError(t, err)
	providerConf := config.GetProviderConf()
	providerConf.TrackQuota = 0
	err = dataprovider.Initialize(providerConf, configDir, true)
	assert.NoError(t, err)
	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = writeSFTPFile(testFileName, 32, client)
		assert.NoError(t, err)
		err = client.Rename(testFileName, testFileName+"1")
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = dataprovider.Close()
	assert.NoError(t, err)
	err = config.LoadConfig(configDir, "")
	assert.NoError(t, err)
	providerConf = config.GetProviderConf()
	err = dataprovider.Initialize(providerConf, configDir, true)
	assert.NoError(t, err)
}
func TestGetQuotaError(t *testing.T) {
	if dataprovider.GetProviderStatus().Driver == "memory" {
		t.Skip("this test is not available with the memory provider")
	}
	u := getTestUser()
	u.TotalDataTransfer = 2000
	mappedPath := filepath.Join(os.TempDir(), "vdir")
	folderName := filepath.Base(mappedPath)
	vdirPath := "/vpath"
	f := vfs.BaseVirtualFolder{
		Name:       folderName,
		MappedPath: mappedPath,
	}
	_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
	assert.NoError(t, err)
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName,
		},
		VirtualPath: vdirPath,
		QuotaSize:   0,
		QuotaFiles:  10,
	})
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = writeSFTPFile(testFileName, 32, client)
		assert.NoError(t, err)
		err = dataprovider.Close()
		assert.NoError(t, err)
		err = client.Rename(testFileName, path.Join(vdirPath, testFileName))
		assert.Error(t, err)
		err = config.LoadConfig(configDir, "")
		assert.NoError(t, err)
		providerConf := config.GetProviderConf()
		err = dataprovider.Initialize(providerConf, configDir, true)
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPath)
	assert.NoError(t, err)
}
func TestRetentionAPI(t *testing.T) {
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	uploadPath := path.Join(testDir, testFileName)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = writeSFTPFile(uploadPath, 32, client)
		assert.NoError(t, err)
		folderRetention := []dataprovider.FolderRetention{
			{
				Path:            "/",
				Retention:       24,
				DeleteEmptyDirs: true,
			},
		}
		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return len(common.RetentionChecks.Get("")) == 0
		}, 1000*time.Millisecond, 50*time.Millisecond)
		_, err = client.Stat(uploadPath)
		assert.NoError(t, err)
		err = client.Chtimes(uploadPath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
		assert.NoError(t, err)
		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return len(common.RetentionChecks.Get("")) == 0
		}, 1000*time.Millisecond, 50*time.Millisecond)
		_, err = client.Stat(uploadPath)
		assert.ErrorIs(t, err, os.ErrNotExist)
		_, err = client.Stat(testDir)
		assert.ErrorIs(t, err, os.ErrNotExist)
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = writeSFTPFile(uploadPath, 32, client)
		assert.NoError(t, err)
		folderRetention[0].DeleteEmptyDirs = false
		err = client.Chtimes(uploadPath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
		assert.NoError(t, err)
		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return len(common.RetentionChecks.Get("")) == 0
		}, 1000*time.Millisecond, 50*time.Millisecond)
		_, err = client.Stat(uploadPath)
		assert.ErrorIs(t, err, os.ErrNotExist)
		_, err = client.Stat(testDir)
		assert.NoError(t, err)
		err = writeSFTPFile(uploadPath, 32, client)
		assert.NoError(t, err)
		err = client.Chtimes(uploadPath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
		assert.NoError(t, err)
	}
	// remove delete permissions to the user
	user.Permissions["/"+testDir] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
		dataprovider.PermCreateDirs, dataprovider.PermChtimes}
	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		innerUploadFilePath := path.Join("/"+testDir, testDir, testFileName)
		err = client.Mkdir(path.Join(testDir, testDir))
		assert.NoError(t, err)
		err = writeSFTPFile(innerUploadFilePath, 32, client)
		assert.NoError(t, err)
		err = client.Chtimes(innerUploadFilePath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
		assert.NoError(t, err)
		folderRetention := []dataprovider.FolderRetention{
			{
				Path:      "/missing",
				Retention: 24,
			},
			{
				Path:            "/" + testDir,
				Retention:       24,
				DeleteEmptyDirs: true,
			},
			{
				Path:                  path.Dir(innerUploadFilePath),
				Retention:             0,
				IgnoreUserPermissions: true,
			},
		}
		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return len(common.RetentionChecks.Get("")) == 0
		}, 1000*time.Millisecond, 50*time.Millisecond)
		_, err = client.Stat(uploadPath)
		assert.NoError(t, err)
		_, err = client.Stat(innerUploadFilePath)
		assert.NoError(t, err)
		folderRetention[1].IgnoreUserPermissions = true
		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return len(common.RetentionChecks.Get("")) == 0
		}, 1000*time.Millisecond, 50*time.Millisecond)
		_, err = client.Stat(uploadPath)
		assert.ErrorIs(t, err, os.ErrNotExist)
		_, err = client.Stat(innerUploadFilePath)
		assert.NoError(t, err)
		folderRetention = []dataprovider.FolderRetention{
			{
				Path:                  "/" + testDir,
				Retention:             24,
				DeleteEmptyDirs:       true,
				IgnoreUserPermissions: true,
			},
		}
		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return len(common.RetentionChecks.Get("")) == 0
		}, 1000*time.Millisecond, 50*time.Millisecond)
		_, err = client.Stat(innerUploadFilePath)
		assert.ErrorIs(t, err, os.ErrNotExist)
	}
	// finally test some errors removing files or folders
	if runtime.GOOS != osWindows {
		dirPath := filepath.Join(user.HomeDir, "adir", "sub")
		err := os.MkdirAll(dirPath, os.ModePerm)
		assert.NoError(t, err)
		filePath := filepath.Join(dirPath, "f.dat")
		err = os.WriteFile(filePath, nil, os.ModePerm)
		assert.NoError(t, err)
		err = os.Chtimes(filePath, time.Now().Add(-72*time.Hour), time.Now().Add(-72*time.Hour))
		assert.NoError(t, err)
		err = os.Chmod(dirPath, 0001)
		assert.NoError(t, err)
		folderRetention := []dataprovider.FolderRetention{
			{
				Path:                  "/adir",
				Retention:             24,
				DeleteEmptyDirs:       true,
				IgnoreUserPermissions: true,
			},
		}
		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return len(common.RetentionChecks.Get("")) == 0
		}, 1000*time.Millisecond, 50*time.Millisecond)
		err = os.Chmod(dirPath, 0555)
		assert.NoError(t, err)
		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return len(common.RetentionChecks.Get("")) == 0
		}, 1000*time.Millisecond, 50*time.Millisecond)
		err = os.Chmod(dirPath, os.ModePerm)
		assert.NoError(t, err)
		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
		assert.NoError(t, err)
		assert.Eventually(t, func() bool {
			return len(common.RetentionChecks.Get("")) == 0
		}, 1000*time.Millisecond, 50*time.Millisecond)
		assert.NoDirExists(t, dirPath)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestRenameDir(t *testing.T) {
	u := getTestUser()
	testDir := "/dir-to-rename"
	u.Permissions[testDir] = []string{dataprovider.PermListItems, dataprovider.PermUpload}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, testFileName), 32, client)
		assert.NoError(t, err)
		err = client.Rename(testDir, testDir+"_rename")
		assert.ErrorIs(t, err, os.ErrPermission)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestBuiltinKeyboardInteractiveAuthentication(t *testing.T) {
	u := getTestUser()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	authMethods := []ssh.AuthMethod{
		ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
			return []string{defaultPassword}, nil
		}),
	}
	conn, client, err := getCustomAuthSftpClient(user, authMethods)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		assert.NoError(t, checkBasicSFTP(client))
		err = writeSFTPFile(testFileName, 4096, client)
		assert.NoError(t, err)
	}
	// add multi-factor authentication
	configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
	assert.NoError(t, err)
	user.Password = defaultPassword
	user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
		Enabled:    true,
		ConfigName: configName,
		Secret:     kms.NewPlainSecret(secret),
		Protocols:  []string{common.ProtocolSSH},
	}
	err = dataprovider.UpdateUser(&user, "", "", "")
	assert.NoError(t, err)
	passcode, err := generateTOTPPasscode(secret, otp.AlgorithmSHA1)
	assert.NoError(t, err)
	passwordAsked := false
	passcodeAsked := false
	authMethods = []ssh.AuthMethod{
		ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
			var answers []string
			if strings.HasPrefix(questions[0], "Password") {
				answers = append(answers, defaultPassword)
				passwordAsked = true
			} else {
				answers = append(answers, passcode)
				passcodeAsked = true
			}
			return answers, nil
		}),
	}
	conn, client, err = getCustomAuthSftpClient(user, authMethods)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		assert.NoError(t, checkBasicSFTP(client))
		err = writeSFTPFile(testFileName, 4096, client)
		assert.NoError(t, err)
	}
	assert.True(t, passwordAsked)
	assert.True(t, passcodeAsked)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestRenameSymlink(t *testing.T) {
	u := getTestUser()
	testDir := "/dir-no-create-links"
	otherDir := "otherdir"
	u.Permissions[testDir] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete,
		dataprovider.PermCreateDirs}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.Mkdir(otherDir)
		assert.NoError(t, err)
		err = client.Symlink(otherDir, otherDir+".link")
		assert.NoError(t, err)
		err = client.Rename(otherDir+".link", path.Join(testDir, "symlink"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(otherDir+".link", "allowed_link")
		assert.NoError(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestSplittedDeletePerms(t *testing.T) {
	u := getTestUser()
	u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDeleteDirs,
		dataprovider.PermCreateDirs}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = writeSFTPFile(testFileName, 4096, client)
		assert.NoError(t, err)
		err = client.Remove(testFileName)
		assert.Error(t, err)
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = client.RemoveDirectory(testDir)
		assert.NoError(t, err)
	}
	u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDeleteFiles,
		dataprovider.PermCreateDirs, dataprovider.PermOverwrite}
	_, _, err = httpdtest.UpdateUser(u, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = writeSFTPFile(testFileName, 4096, client)
		assert.NoError(t, err)
		err = client.Remove(testFileName)
		assert.NoError(t, err)
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = client.RemoveDirectory(testDir)
		assert.Error(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestSplittedRenamePerms(t *testing.T) {
	u := getTestUser()
	u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermRenameDirs,
		dataprovider.PermCreateDirs}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = writeSFTPFile(testFileName, 4096, client)
		assert.NoError(t, err)
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = client.Rename(testFileName, testFileName+"_renamed")
		assert.Error(t, err)
		err = client.Rename(testDir, testDir+"_renamed")
		assert.NoError(t, err)
	}
	u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermRenameFiles,
		dataprovider.PermCreateDirs, dataprovider.PermOverwrite}
	_, _, err = httpdtest.UpdateUser(u, http.StatusOK, "")
	assert.NoError(t, err)
	conn, client, err = getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = writeSFTPFile(testFileName, 4096, client)
		assert.NoError(t, err)
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = client.Rename(testFileName, testFileName+"_renamed")
		assert.NoError(t, err)
		err = client.Rename(testDir, testDir+"_renamed")
		assert.Error(t, err)
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestSFTPLoopError(t *testing.T) {
	smtpCfg := smtp.Config{
		Host:          "127.0.0.1",
		Port:          2525,
		From:          "notification@example.com",
		TemplatesPath: "templates",
	}
	err := smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
	user1 := getTestUser()
	user2 := getTestUser()
	user1.Username += "1"
	user2.Username += "2"
	// user1 is a local account with a virtual SFTP folder to user2
	// user2 has user1 as SFTP fs
	f := vfs.BaseVirtualFolder{
		Name: "sftp",
		FsConfig: vfs.Filesystem{
			Provider: sdk.SFTPFilesystemProvider,
			SFTPConfig: vfs.SFTPFsConfig{
				BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
					Endpoint: sftpServerAddr,
					Username: user2.Username,
				},
				Password: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	folder, _, err := httpdtest.AddFolder(f, http.StatusCreated)
	assert.NoError(t, err)
	user1.VirtualFolders = append(user1.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folder.Name,
		},
		VirtualPath: "/vdir",
	})
	user2.FsConfig.Provider = sdk.SFTPFilesystemProvider
	user2.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
		BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
			Endpoint: sftpServerAddr,
			Username: user1.Username,
		},
		Password: kms.NewPlainSecret(defaultPassword),
	}
	user1, resp, err := httpdtest.AddUser(user1, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	user2, resp, err = httpdtest.AddUser(user2, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	// test metadata check event error
	a1 := dataprovider.BaseEventAction{
		Name: "a1",
		Type: dataprovider.ActionTypeMetadataCheck,
	}
	action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
	assert.NoError(t, err)
	a2 := dataprovider.BaseEventAction{
		Name: "a2",
		Type: dataprovider.ActionTypeEmail,
		Options: dataprovider.BaseEventActionOptions{
			EmailConfig: dataprovider.EventActionEmailConfig{
				Recipients: []string{"failure@example.com"},
				Subject:    `Failed action"`,
				Body:       "Test body",
			},
		},
	}
	action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
	assert.NoError(t, err)
	r1 := dataprovider.EventRule{
		Name:    "rule1",
		Status:  1,
		Trigger: dataprovider.EventTriggerProviderEvent,
		Conditions: dataprovider.EventConditions{
			ProviderEvents: []string{"update"},
		},
		Actions: []dataprovider.EventAction{
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action1.Name,
				},
				Order: 1,
			},
			{
				BaseEventAction: dataprovider.BaseEventAction{
					Name: action2.Name,
				},
				Order: 2,
				Options: dataprovider.EventActionOptions{
					IsFailureAction: true,
				},
			},
		},
	}
	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
	assert.NoError(t, err)
	lastReceivedEmail.reset()
	_, _, err = httpdtest.UpdateUser(user2, http.StatusOK, "")
	assert.NoError(t, err)
	assert.Eventually(t, func() bool {
		return lastReceivedEmail.get().From != ""
	}, 3000*time.Millisecond, 100*time.Millisecond)
	email := lastReceivedEmail.get()
	assert.Len(t, email.To, 1)
	assert.True(t, util.Contains(email.To, "failure@example.com"))
	assert.Contains(t, email.Data, `Subject: Failed action`)
	user1.VirtualFolders[0].FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
	user2.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
	conn := common.NewBaseConnection("", common.ProtocolWebDAV, "", "", user1)
	_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
	assert.ErrorIs(t, err, os.ErrPermission)
	conn = common.NewBaseConnection("", common.ProtocolSFTP, "", "", user1)
	_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
	assert.Error(t, err)
	conn = common.NewBaseConnection("", common.ProtocolFTP, "", "", user1)
	_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
	assert.Error(t, err)
	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user1, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user1.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user2, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user2.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(folder, http.StatusOK)
	assert.NoError(t, err)
	smtpCfg = smtp.Config{}
	err = smtpCfg.Initialize(configDir, true)
	require.NoError(t, err)
}
func TestNonLocalCrossRename(t *testing.T) {
	baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err, string(resp))
	u := getTestUser()
	u.HomeDir += "_folders"
	u.Username += "_folders"
	mappedPathSFTP := filepath.Join(os.TempDir(), "sftp")
	folderNameSFTP := filepath.Base(mappedPathSFTP)
	vdirSFTPPath := "/vdir/sftp"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderNameSFTP,
		},
		VirtualPath: vdirSFTPPath,
	})
	mappedPathCrypt := filepath.Join(os.TempDir(), "crypt")
	folderNameCrypt := filepath.Base(mappedPathCrypt)
	vdirCryptPath := "/vdir/crypt"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderNameCrypt,
		},
		VirtualPath: vdirCryptPath,
	})
	f1 := vfs.BaseVirtualFolder{
		Name: folderNameSFTP,
		FsConfig: vfs.Filesystem{
			Provider: sdk.SFTPFilesystemProvider,
			SFTPConfig: vfs.SFTPFsConfig{
				BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
					Endpoint: sftpServerAddr,
					Username: baseUser.Username,
				},
				Password: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name: folderNameCrypt,
		FsConfig: vfs.Filesystem{
			Provider: sdk.CryptedFilesystemProvider,
			CryptConfig: vfs.CryptFsConfig{
				Passphrase: kms.NewPlainSecret(defaultPassword),
			},
		},
		MappedPath: mappedPathCrypt,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		assert.NoError(t, checkBasicSFTP(client))
		err = writeSFTPFile(testFileName, 4096, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirSFTPPath, testFileName), 8192, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirCryptPath, testFileName), 16384, client)
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirSFTPPath, testFileName), path.Join(vdirCryptPath, testFileName+".rename"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirSFTPPath, testFileName+".rename"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(testFileName, path.Join(vdirCryptPath, testFileName+".rename"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(testFileName, path.Join(vdirSFTPPath, testFileName+".rename"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(path.Join(vdirSFTPPath, testFileName), testFileName+".rename")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(path.Join(vdirCryptPath, testFileName), testFileName+".rename")
		assert.ErrorIs(t, err, os.ErrPermission)
		// rename on local fs or on the same folder must work
		err = client.Rename(testFileName, testFileName+".rename")
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirSFTPPath, testFileName), path.Join(vdirSFTPPath, testFileName+"_rename"))
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirCryptPath, testFileName+"_rename"))
		assert.NoError(t, err)
		// renaming a virtual folder is not allowed
		err = client.Rename(vdirSFTPPath, vdirSFTPPath+"_rename")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(vdirCryptPath, vdirCryptPath+"_rename")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(vdirCryptPath, path.Join(vdirCryptPath, "rename"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Mkdir(path.Join(vdirCryptPath, "subcryptdir"))
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirCryptPath, "subcryptdir"), vdirCryptPath)
		assert.ErrorIs(t, err, os.ErrPermission)
		// renaming root folder is not allowed
		err = client.Rename("/", "new_name")
		assert.ErrorIs(t, err, os.ErrPermission)
		// renaming a path to a virtual folder is not allowed
		err = client.Rename("/vdir", "new_vdir")
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameCrypt}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameSFTP}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(baseUser.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPathCrypt)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPathSFTP)
	assert.NoError(t, err)
}
func TestNonLocalCrossRenameNonLocalBaseUser(t *testing.T) {
	baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err, string(resp))
	u := getTestSFTPUser()
	mappedPathLocal := filepath.Join(os.TempDir(), "local")
	folderNameLocal := filepath.Base(mappedPathLocal)
	vdirLocalPath := "/vdir/local"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderNameLocal,
		},
		VirtualPath: vdirLocalPath,
	})
	mappedPathCrypt := filepath.Join(os.TempDir(), "crypt")
	folderNameCrypt := filepath.Base(mappedPathCrypt)
	vdirCryptPath := "/vdir/crypt"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderNameCrypt,
		},
		VirtualPath: vdirCryptPath,
	})
	f1 := vfs.BaseVirtualFolder{
		Name:       folderNameLocal,
		MappedPath: mappedPathLocal,
	}
	_, _, err = httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name: folderNameCrypt,
		FsConfig: vfs.Filesystem{
			Provider: sdk.CryptedFilesystemProvider,
			CryptConfig: vfs.CryptFsConfig{
				Passphrase: kms.NewPlainSecret(defaultPassword),
			},
		},
		MappedPath: mappedPathCrypt,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		assert.NoError(t, checkBasicSFTP(client))
		err = writeSFTPFile(testFileName, 4096, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirLocalPath, testFileName), 8192, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vdirCryptPath, testFileName), 16384, client)
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirLocalPath, testFileName), path.Join(vdirCryptPath, testFileName+".rename"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirLocalPath, testFileName+".rename"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(testFileName, path.Join(vdirCryptPath, testFileName+".rename"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(testFileName, path.Join(vdirLocalPath, testFileName+".rename"))
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(path.Join(vdirLocalPath, testFileName), testFileName+".rename")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(path.Join(vdirCryptPath, testFileName), testFileName+".rename")
		assert.ErrorIs(t, err, os.ErrPermission)
		// rename on local fs or on the same folder must work
		err = client.Rename(testFileName, testFileName+".rename")
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirLocalPath, testFileName), path.Join(vdirLocalPath, testFileName+"_rename"))
		assert.NoError(t, err)
		err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirCryptPath, testFileName+"_rename"))
		assert.NoError(t, err)
		// renaming a virtual folder is not allowed
		err = client.Rename(vdirLocalPath, vdirLocalPath+"_rename")
		assert.ErrorIs(t, err, os.ErrPermission)
		err = client.Rename(vdirCryptPath, vdirCryptPath+"_rename")
		assert.ErrorIs(t, err, os.ErrPermission)
		// renaming root folder is not allowed
		err = client.Rename("/", "new_name")
		assert.ErrorIs(t, err, os.ErrPermission)
		// renaming a path to a virtual folder is not allowed
		err = client.Rename("/vdir", "new_vdir")
		if assert.Error(t, err) {
			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
		}
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameCrypt}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameLocal}, http.StatusOK)
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(baseUser.GetHomeDir())
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPathCrypt)
	assert.NoError(t, err)
	err = os.RemoveAll(mappedPathLocal)
	assert.NoError(t, err)
}
func TestCopyAndRemoveSSHCommands(t *testing.T) {
	u := getTestUser()
	u.QuotaFiles = 1000
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		fileSize := int64(32)
		err = writeSFTPFile(testFileName, fileSize, client)
		assert.NoError(t, err)
		testFileNameCopy := testFileName + "_copy"
		out, err := runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, testFileNameCopy), user)
		assert.NoError(t, err, string(out))
		// the resolved destination path match the source path
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, path.Dir(testFileName)), user)
		assert.Error(t, err, string(out))
		info, err := client.Stat(testFileNameCopy)
		if assert.NoError(t, err) {
			assert.Equal(t, fileSize, info.Size())
		}
		testDir := "test dir"
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s '%s'`, testFileName, testDir), user)
		assert.NoError(t, err, string(out))
		info, err = client.Stat(path.Join(testDir, testFileName))
		if assert.NoError(t, err) {
			assert.Equal(t, fileSize, info.Size())
		}
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 3*fileSize, user.UsedQuotaSize)
		assert.Equal(t, 3, user.UsedQuotaFiles)
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %s", testFileNameCopy), user)
		assert.NoError(t, err, string(out))
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-remove '%s'`, testDir), user)
		assert.NoError(t, err, string(out))
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, fileSize, user.UsedQuotaSize)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		_, err = client.Stat(testFileNameCopy)
		assert.ErrorIs(t, err, os.ErrNotExist)
		// create a dir tree
		dir1 := "dir1"
		dir2 := "dir 2"
		err = client.MkdirAll(path.Join(dir1, dir2))
		assert.NoError(t, err)
		toCreate := []string{
			path.Join(dir1, testFileName),
			path.Join(dir1, dir2, testFileName),
		}
		for _, p := range toCreate {
			err = writeSFTPFile(p, fileSize, client)
			assert.NoError(t, err)
		}
		// create a symlink, copying a symlink is not supported
		err = client.Symlink(path.Join("/", dir1, testFileName), path.Join("/", dir1, testFileName+"_link"))
		assert.NoError(t, err)
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", dir1, testFileName+"_link"),
			path.Join("/", testFileName+"_link")), user)
		assert.Error(t, err, string(out))
		// copying a dir inside itself should fail
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", dir1),
			path.Join("/", dir1, "sub")), user)
		assert.Error(t, err, string(out))
		// copy source and dest must differ
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", dir1),
			path.Join("/", dir1)), user)
		assert.Error(t, err, string(out))
		// copy a missing file/dir should fail
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", "missing_entry"),
			path.Join("/", dir1)), user)
		assert.Error(t, err, string(out))
		// try to overwrite a file with a dir
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", dir1), testFileName), user)
		assert.Error(t, err, string(out))
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s "%s"`, dir1, dir2), user)
		assert.NoError(t, err, string(out))
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 5*fileSize, user.UsedQuotaSize)
		assert.Equal(t, 5, user.UsedQuotaFiles)
		// copy again, quota must remain unchanged
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s/ "%s"`, dir1, dir2), user)
		assert.NoError(t, err, string(out))
		_, err = client.Stat(dir2)
		assert.NoError(t, err)
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 5*fileSize, user.UsedQuotaSize)
		assert.Equal(t, 5, user.UsedQuotaFiles)
		// now copy inside target
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s "%s"`, dir1, dir2), user)
		assert.NoError(t, err, string(out))
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, 7*fileSize, user.UsedQuotaSize)
		assert.Equal(t, 7, user.UsedQuotaFiles)
		for _, p := range []string{dir1, dir2} {
			out, err = runSSHCommand(fmt.Sprintf(`sftpgo-remove "%s"`, p), user)
			assert.NoError(t, err, string(out))
			_, err = client.Stat(p)
			assert.ErrorIs(t, err, os.ErrNotExist)
		}
		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
		assert.NoError(t, err)
		assert.Equal(t, fileSize, user.UsedQuotaSize)
		assert.Equal(t, 1, user.UsedQuotaFiles)
		// test quota errors
		user.QuotaFiles = 1
		_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
		assert.NoError(t, err)
		// quota files exceeded
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, testFileNameCopy), user)
		assert.Error(t, err, string(out))
		user.QuotaFiles = 1000
		user.QuotaSize = fileSize + 1
		_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
		assert.NoError(t, err)
		// quota size exceeded after the copy
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, testFileNameCopy), user)
		assert.Error(t, err, string(out))
		user.QuotaSize = fileSize - 1
		_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
		assert.NoError(t, err)
		// quota size exceeded
		out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, testFileNameCopy), user)
		assert.Error(t, err, string(out))
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestCopyAndRemovePermissions(t *testing.T) {
	u := getTestUser()
	restrictedPath := "/dir/path"
	patternFilterPath := "/patterns"
	u.Filters.FilePatterns = []sdk.PatternsFilter{
		{
			Path:           patternFilterPath,
			DeniedPatterns: []string{"*.dat"},
		},
	}
	u.Permissions[restrictedPath] = []string{}
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		err = client.MkdirAll(restrictedPath)
		assert.NoError(t, err)
		err = client.MkdirAll(patternFilterPath)
		assert.NoError(t, err)
		err = writeSFTPFile(testFileName, 100, client)
		assert.NoError(t, err)
		// getting file writer will fail
		out, err := runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, testFileName, restrictedPath), user)
		assert.Error(t, err, string(out))
		// file pattern not allowed
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, testFileName, patternFilterPath), user)
		assert.Error(t, err, string(out))
		testDir := path.Join("/", path.Base(restrictedPath))
		err = client.Mkdir(testDir)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(testDir, testFileName), 100, client)
		assert.NoError(t, err)
		// creating target dir will fail
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s/`, testDir, restrictedPath), user)
		assert.Error(t, err, string(out))
		// get dir contents will fail
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s /`, restrictedPath), user)
		assert.Error(t, err, string(out))
		// get dir contents will fail
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-remove %s`, restrictedPath), user)
		assert.Error(t, err, string(out))
		// give list dir permissions and retry, now delete will fail
		user.Permissions[restrictedPath] = []string{dataprovider.PermListItems, dataprovider.PermUpload}
		user.Permissions[testDir] = []string{dataprovider.PermListItems}
		_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
		assert.NoError(t, err)
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, testFileName, restrictedPath), user)
		assert.NoError(t, err, string(out))
		// overwrite will fail, no permission
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, testFileName, restrictedPath), user)
		assert.Error(t, err, string(out))
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-remove %s`, restrictedPath), user)
		assert.Error(t, err, string(out))
		// try to copy a file from testDir, we have only list permissions so getFileReader will fail
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, path.Join(testDir, testFileName), testFileName+".copy"), user)
		assert.Error(t, err, string(out))
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestCrossFoldersCopy(t *testing.T) {
	baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
	assert.NoError(t, err, string(resp))
	u := getTestUser()
	u.Username += "_1"
	u.HomeDir = filepath.Join(os.TempDir(), u.Username)
	u.QuotaFiles = 1000
	mappedPath1 := filepath.Join(os.TempDir(), "mapped1")
	folderName1 := filepath.Base(mappedPath1)
	vpath1 := "/vdirs/vdir1"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName1,
		},
		VirtualPath: vpath1,
		QuotaSize:   -1,
		QuotaFiles:  -1,
	})
	mappedPath2 := filepath.Join(os.TempDir(), "mapped1", "dir", "mapped2")
	folderName2 := filepath.Base(mappedPath2)
	vpath2 := "/vdirs/vdir2"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName2,
		},
		VirtualPath: vpath2,
		QuotaSize:   -1,
		QuotaFiles:  -1,
	})
	mappedPath3 := filepath.Join(os.TempDir(), "mapped3")
	folderName3 := filepath.Base(mappedPath3)
	vpath3 := "/vdirs/vdir3"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName3,
		},
		VirtualPath: vpath3,
		QuotaSize:   -1,
		QuotaFiles:  -1,
	})
	mappedPath4 := filepath.Join(os.TempDir(), "mapped4")
	folderName4 := filepath.Base(mappedPath4)
	vpath4 := "/vdirs/vdir4"
	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			Name: folderName4,
		},
		VirtualPath: vpath4,
		QuotaSize:   -1,
		QuotaFiles:  -1,
	})
	f1 := vfs.BaseVirtualFolder{
		Name:       folderName1,
		MappedPath: mappedPath1,
	}
	_, _, err = httpdtest.AddFolder(f1, http.StatusCreated)
	assert.NoError(t, err)
	f2 := vfs.BaseVirtualFolder{
		Name:       folderName2,
		MappedPath: mappedPath2,
	}
	_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
	assert.NoError(t, err)
	f3 := vfs.BaseVirtualFolder{
		Name:       folderName3,
		MappedPath: mappedPath3,
		FsConfig: vfs.Filesystem{
			Provider: sdk.CryptedFilesystemProvider,
			CryptConfig: vfs.CryptFsConfig{
				Passphrase: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f3, http.StatusCreated)
	assert.NoError(t, err)
	f4 := vfs.BaseVirtualFolder{
		Name:       folderName4,
		MappedPath: mappedPath4,
		FsConfig: vfs.Filesystem{
			Provider: sdk.SFTPFilesystemProvider,
			SFTPConfig: vfs.SFTPFsConfig{
				BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
					Endpoint: sftpServerAddr,
					Username: baseUser.Username,
				},
				Password: kms.NewPlainSecret(defaultPassword),
			},
		},
	}
	_, _, err = httpdtest.AddFolder(f4, http.StatusCreated)
	assert.NoError(t, err)
	user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err, string(resp))
	conn, client, err := getSftpClient(user)
	if assert.NoError(t, err) {
		defer conn.Close()
		defer client.Close()
		baseFileSize := int64(100)
		err = writeSFTPFile(path.Join(vpath1, testFileName), baseFileSize+1, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vpath2, testFileName), baseFileSize+2, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vpath3, testFileName), baseFileSize+3, client)
		assert.NoError(t, err)
		err = writeSFTPFile(path.Join(vpath4, testFileName), baseFileSize+4, client)
		assert.NoError(t, err)
		// cannot remove a directory with virtual folders inside
		out, err := runSSHCommand(fmt.Sprintf(`sftpgo-remove %s`, path.Dir(vpath1)), user)
		assert.Error(t, err, string(out))
		// copy across virtual folders
		copyDir := "/copy"
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s/`, path.Dir(vpath1), copyDir), user)
		assert.NoError(t, err, string(out))
		// check the copy
		info, err := client.Stat(path.Join(copyDir, vpath1, testFileName))
		if assert.NoError(t, err) {
			assert.Equal(t, baseFileSize+1, info.Size())
		}
		info, err = client.Stat(path.Join(copyDir, vpath2, testFileName))
		if assert.NoError(t, err) {
			assert.Equal(t, baseFileSize+2, info.Size())
		}
		info, err = client.Stat(path.Join(copyDir, vpath3, testFileName))
		if assert.NoError(t, err) {
			assert.Equal(t, baseFileSize+3, info.Size())
		}
		info, err = client.Stat(path.Join(copyDir, vpath4, testFileName))
		if assert.NoError(t, err) {
			assert.Equal(t, baseFileSize+4, info.Size())
		}
		// nested fs paths
		out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, vpath1, vpath2), user)
		assert.Error(t, err, string(out))
	}
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(baseUser.GetHomeDir())
	assert.NoError(t, err)
	for _, folderName := range []string{folderName1, folderName2, folderName3, folderName4} {
		_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
		assert.NoError(t, err)
		err = os.RemoveAll(filepath.Join(os.TempDir(), folderName))
		assert.NoError(t, err)
	}
}
func TestHTTPFs(t *testing.T) {
	u := getTestUserWithHTTPFs()
	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
	assert.NoError(t, err)
	err = os.MkdirAll(user.GetHomeDir(), os.ModePerm)
	assert.NoError(t, err)
	conn := common.NewBaseConnection(xid.New().String(), common.ProtocolFTP, "", "", user)
	err = conn.CreateDir(httpFsWellKnowDir, false)
	assert.NoError(t, err)
	err = os.WriteFile(filepath.Join(os.TempDir(), "httpfs", defaultHTTPFsUsername, httpFsWellKnowDir, "file.txt"), []byte("data"), 0666)
	assert.NoError(t, err)
	err = conn.Copy(httpFsWellKnowDir, httpFsWellKnowDir+"_copy")
	assert.NoError(t, err)
	_, err = httpdtest.RemoveUser(user, http.StatusOK)
	assert.NoError(t, err)
	err = os.RemoveAll(user.GetHomeDir())
	assert.NoError(t, err)
}
func TestProxyProtocol(t *testing.T) {
	resp, err := httpclient.Get(fmt.Sprintf("http://%v", httpProxyAddr))
	if assert.NoError(t, err) {
		defer resp.Body.Close()
		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	}
}
func TestSetProtocol(t *testing.T) {
	conn := common.NewBaseConnection("id", "sshd_exec", "", "", dataprovider.User{BaseUser: sdk.BaseUser{HomeDir: os.TempDir()}})
	conn.SetProtocol(common.ProtocolSCP)
	require.Equal(t, "SCP_id", conn.GetID())
}
func TestGetFsError(t *testing.T) {
	u := getTestUser()
	u.FsConfig.Provider = sdk.GCSFilesystemProvider
	u.FsConfig.GCSConfig.Bucket = "test"
	u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
	conn := common.NewBaseConnection("", common.ProtocolFTP, "", "", u)
	_, _, err := conn.GetFsAndResolvedPath("/vpath")
	assert.Error(t, err)
}
func waitTCPListening(address string) {
	for {
		conn, err := net.Dial("tcp", address)
		if err != nil {
			logger.WarnToConsole("tcp server %v not listening: %v", address, err)
			time.Sleep(100 * time.Millisecond)
			continue
		}
		logger.InfoToConsole("tcp server %v now listening", address)
		conn.Close()
		break
	}
}
func checkBasicSFTP(client *sftp.Client) error {
	_, err := client.Getwd()
	if err != nil {
		return err
	}
	_, err = client.ReadDir(".")
	return err
}
func getCustomAuthSftpClient(user dataprovider.User, authMethods []ssh.AuthMethod) (*ssh.Client, *sftp.Client, error) {
	var sftpClient *sftp.Client
	config := &ssh.ClientConfig{
		User: user.Username,
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
		Auth:    authMethods,
		Timeout: 5 * time.Second,
	}
	conn, err := ssh.Dial("tcp", sftpServerAddr, config)
	if err != nil {
		return conn, sftpClient, err
	}
	sftpClient, err = sftp.NewClient(conn)
	if err != nil {
		conn.Close()
	}
	return conn, sftpClient, err
}
func getSftpClient(user dataprovider.User) (*ssh.Client, *sftp.Client, error) {
	var sftpClient *sftp.Client
	config := &ssh.ClientConfig{
		User: user.Username,
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
		Timeout: 5 * time.Second,
	}
	if user.Password != "" {
		config.Auth = []ssh.AuthMethod{ssh.Password(user.Password)}
	} else {
		config.Auth = []ssh.AuthMethod{ssh.Password(defaultPassword)}
	}
	conn, err := ssh.Dial("tcp", sftpServerAddr, config)
	if err != nil {
		return conn, sftpClient, err
	}
	sftpClient, err = sftp.NewClient(conn)
	if err != nil {
		conn.Close()
	}
	return conn, sftpClient, err
}
func runSSHCommand(command string, user dataprovider.User) ([]byte, error) {
	var sshSession *ssh.Session
	var output []byte
	config := &ssh.ClientConfig{
		User: user.Username,
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
		Timeout: 5 * time.Second,
	}
	if user.Password != "" {
		config.Auth = []ssh.AuthMethod{ssh.Password(user.Password)}
	} else {
		config.Auth = []ssh.AuthMethod{ssh.Password(defaultPassword)}
	}
	conn, err := ssh.Dial("tcp", sftpServerAddr, config)
	if err != nil {
		return output, err
	}
	defer conn.Close()
	sshSession, err = conn.NewSession()
	if err != nil {
		return output, err
	}
	var stdout, stderr bytes.Buffer
	sshSession.Stdout = &stdout
	sshSession.Stderr = &stderr
	err = sshSession.Run(command)
	if err != nil {
		return nil, fmt.Errorf("failed to run command %v: %v", command, stderr.Bytes())
	}
	return stdout.Bytes(), err
}
func getWebDavClient(user dataprovider.User) *gowebdav.Client {
	rootPath := fmt.Sprintf("http://localhost:%d/", webDavServerPort)
	pwd := defaultPassword
	if user.Password != "" {
		pwd = user.Password
	}
	client := gowebdav.NewClient(rootPath, user.Username, pwd)
	client.SetTimeout(10 * time.Second)
	return client
}
func getTestUser() dataprovider.User {
	user := dataprovider.User{
		BaseUser: sdk.BaseUser{
			Username:       defaultUsername,
			Password:       defaultPassword,
			HomeDir:        filepath.Join(homeBasePath, defaultUsername),
			Status:         1,
			ExpirationDate: 0,
		},
	}
	user.Permissions = make(map[string][]string)
	user.Permissions["/"] = allPerms
	return user
}
func getTestSFTPUser() dataprovider.User {
	u := getTestUser()
	u.Username = defaultSFTPUsername
	u.FsConfig.Provider = sdk.SFTPFilesystemProvider
	u.FsConfig.SFTPConfig.Endpoint = sftpServerAddr
	u.FsConfig.SFTPConfig.Username = defaultUsername
	u.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
	return u
}
func getCryptFsUser() dataprovider.User {
	u := getTestUser()
	u.Username += "_crypt"
	u.FsConfig.Provider = sdk.CryptedFilesystemProvider
	u.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret(defaultPassword)
	return u
}
func getTestUserWithHTTPFs() dataprovider.User {
	u := getTestUser()
	u.FsConfig.Provider = sdk.HTTPFilesystemProvider
	u.FsConfig.HTTPConfig = vfs.HTTPFsConfig{
		BaseHTTPFsConfig: sdk.BaseHTTPFsConfig{
			Endpoint: fmt.Sprintf("http://127.0.0.1:%d/api/v1", httpFsPort),
			Username: defaultHTTPFsUsername,
		},
	}
	return u
}
func writeSFTPFile(name string, size int64, client *sftp.Client) error {
	err := writeSFTPFileNoCheck(name, size, client)
	if err != nil {
		return err
	}
	info, err := client.Stat(name)
	if err != nil {
		return err
	}
	if info.Size() != size {
		return fmt.Errorf("file size mismatch, wanted %v, actual %v", size, info.Size())
	}
	return nil
}
func writeSFTPFileNoCheck(name string, size int64, client *sftp.Client) error {
	content := make([]byte, size)
	_, err := rand.Read(content)
	if err != nil {
		return err
	}
	f, err := client.Create(name)
	if err != nil {
		return err
	}
	_, err = io.Copy(f, bytes.NewBuffer(content))
	if err != nil {
		f.Close()
		return err
	}
	return f.Close()
}
func getUploadScriptContent(movedPath, logFilePath string, exitStatus int) []byte {
	content := []byte("#!/bin/sh\n\n")
	content = append(content, []byte("sleep 1\n")...)
	if logFilePath != "" {
		content = append(content, []byte(fmt.Sprintf("echo $@ > %v\n", logFilePath))...)
	}
	content = append(content, []byte(fmt.Sprintf("mv ${SFTPGO_ACTION_PATH} %v\n", movedPath))...)
	content = append(content, []byte(fmt.Sprintf("exit %d", exitStatus))...)
	return content
}
func getSaveProviderObjectScriptContent(outFilePath string, exitStatus int) []byte {
	content := []byte("#!/bin/sh\n\n")
	content = append(content, []byte(fmt.Sprintf("echo ${SFTPGO_OBJECT_DATA} > %v\n", outFilePath))...)
	content = append(content, []byte(fmt.Sprintf("exit %d", exitStatus))...)
	return content
}
func generateTOTPPasscode(secret string, algo otp.Algorithm) (string, error) {
	return totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{
		Period:    30,
		Skew:      1,
		Digits:    otp.DigitsSix,
		Algorithm: algo,
	})
}
func isDbDefenderSupported() bool {
	// SQLite shares the implementation with other SQL-based provider but it makes no sense
	// to use it outside test cases
	switch dataprovider.GetProviderStatus().Driver {
	case dataprovider.MySQLDataProviderName, dataprovider.PGSQLDataProviderName,
		dataprovider.CockroachDataProviderName, dataprovider.SQLiteDataProviderName:
		return true
	default:
		return false
	}
}
func getEncryptedFileSize(size int64) (int64, error) {
	encSize, err := sio.EncryptedSize(uint64(size))
	return int64(encSize) + 33, err
}
func printLatestLogs(maxNumberOfLines int) {
	var lines []string
	f, err := os.Open(logFilePath)
	if err != nil {
		return
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		lines = append(lines, scanner.Text()+"\r\n")
		for len(lines) > maxNumberOfLines {
			lines = lines[1:]
		}
	}
	if scanner.Err() != nil {
		logger.WarnToConsole("Unable to print latest logs: %v", scanner.Err())
		return
	}
	for _, line := range lines {
		logger.DebugToConsole(line)
	}
}
type receivedEmail struct {
	sync.RWMutex
	From string
	To   []string
	Data string
}
func (e *receivedEmail) set(from string, to []string, data []byte) {
	e.Lock()
	defer e.Unlock()
	e.From = from
	e.To = to
	e.Data = strings.ReplaceAll(string(data), "=\r\n", "")
}
func (e *receivedEmail) reset() {
	e.Lock()
	defer e.Unlock()
	e.From = ""
	e.To = nil
	e.Data = ""
}
func (e *receivedEmail) get() receivedEmail {
	e.RLock()
	defer e.RUnlock()
	return receivedEmail{
		From: e.From,
		To:   e.To,
		Data: e.Data,
	}
}
func startHTTPFs() {
	go func() {
		readdirCallback := func(name string) []os.FileInfo {
			if name == httpFsWellKnowDir {
				return []os.FileInfo{vfs.NewFileInfo("ghost.txt", false, 0, time.Unix(0, 0), false)}
			}
			return nil
		}
		callbacks := &httpdtest.HTTPFsCallbacks{
			Readdir: readdirCallback,
		}
		if err := httpdtest.StartTestHTTPFs(httpFsPort, callbacks); err != nil {
			logger.ErrorToConsole("could not start HTTPfs test server: %v", err)
			os.Exit(1)
		}
	}()
	waitTCPListening(fmt.Sprintf(":%d", httpFsPort))
}