package httpd

import (
	"context"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"github.com/go-chi/chi"
	"github.com/stretchr/testify/assert"

	"github.com/drakkan/sftpgo/common"
	"github.com/drakkan/sftpgo/dataprovider"
	"github.com/drakkan/sftpgo/utils"
	"github.com/drakkan/sftpgo/vfs"
)

const (
	invalidURL  = "http://foo\x7f.com/"
	inactiveURL = "http://127.0.0.1:12345"
)

func TestGetRespStatus(t *testing.T) {
	var err error
	err = &dataprovider.MethodDisabledError{}
	respStatus := getRespStatus(err)
	assert.Equal(t, http.StatusForbidden, respStatus)
	err = fmt.Errorf("generic error")
	respStatus = getRespStatus(err)
	assert.Equal(t, http.StatusInternalServerError, respStatus)
}

func TestCheckResponse(t *testing.T) {
	err := checkResponse(http.StatusOK, http.StatusCreated)
	assert.Error(t, err)
	err = checkResponse(http.StatusBadRequest, http.StatusBadRequest)
	assert.NoError(t, err)
}

func TestCheckFolder(t *testing.T) {
	expected := &vfs.BaseVirtualFolder{}
	actual := &vfs.BaseVirtualFolder{}
	err := checkFolder(expected, actual)
	assert.Error(t, err)
	expected.ID = 1
	actual.ID = 2
	err = checkFolder(expected, actual)
	assert.Error(t, err)
	expected.ID = 2
	actual.ID = 2
	expected.MappedPath = "path"
	err = checkFolder(expected, actual)
	assert.Error(t, err)
	expected.MappedPath = ""
	expected.LastQuotaUpdate = 1
	err = checkFolder(expected, actual)
	assert.Error(t, err)
	expected.LastQuotaUpdate = 0
	expected.UsedQuotaFiles = 1
	err = checkFolder(expected, actual)
	assert.Error(t, err)
	expected.UsedQuotaFiles = 0
	expected.UsedQuotaSize = 1
	err = checkFolder(expected, actual)
	assert.Error(t, err)
	expected.UsedQuotaSize = 0
	expected.Users = append(expected.Users, "user1")
	err = checkFolder(expected, actual)
	assert.Error(t, err)
	actual.Users = append(actual.Users, "user2")
	err = checkFolder(expected, actual)
	assert.Error(t, err)
	expected.Users = nil
	actual.Users = nil
}

func TestCheckUser(t *testing.T) {
	expected := &dataprovider.User{}
	actual := &dataprovider.User{}
	actual.Password = "password"
	err := checkUser(expected, actual)
	assert.Error(t, err)
	actual.Password = ""
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.ID = 1
	actual.ID = 2
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.ID = 2
	actual.ID = 2
	expected.Permissions = make(map[string][]string)
	expected.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermDelete, dataprovider.PermDownload}
	actual.Permissions = make(map[string][]string)
	err = checkUser(expected, actual)
	assert.Error(t, err)
	actual.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Permissions["/"] = append(expected.Permissions["/"], dataprovider.PermRename)
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Permissions = make(map[string][]string)
	expected.Permissions["/somedir"] = []string{dataprovider.PermAny}
	actual.Permissions = make(map[string][]string)
	actual.Permissions["/otherdir"] = []string{dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Permissions = make(map[string][]string)
	actual.Permissions = make(map[string][]string)
	actual.FsConfig.Provider = dataprovider.S3FilesystemProvider
	err = checkUser(expected, actual)
	assert.Error(t, err)
	actual.FsConfig.Provider = dataprovider.LocalFilesystemProvider
	expected.VirtualFolders = append(expected.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			MappedPath: os.TempDir(),
		},
		VirtualPath: "/vdir",
	})
	err = checkUser(expected, actual)
	assert.Error(t, err)
	actual.VirtualFolders = append(actual.VirtualFolders, vfs.VirtualFolder{
		BaseVirtualFolder: vfs.BaseVirtualFolder{
			MappedPath: os.TempDir(),
		},
		VirtualPath: "/vdir1",
	})
	err = checkUser(expected, actual)
	assert.Error(t, err)
}

func TestCompareUserFilters(t *testing.T) {
	expected := &dataprovider.User{}
	actual := &dataprovider.User{}
	actual.ID = 1
	expected.ID = 1
	expected.Filters.AllowedIP = []string{}
	actual.Filters.AllowedIP = []string{"192.168.1.2/32"}
	err := checkUser(expected, actual)
	assert.Error(t, err)
	expected.Filters.AllowedIP = []string{"192.168.1.3/32"}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Filters.AllowedIP = []string{}
	actual.Filters.AllowedIP = []string{}
	expected.Filters.DeniedIP = []string{}
	actual.Filters.DeniedIP = []string{"192.168.1.2/32"}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Filters.DeniedIP = []string{"192.168.1.3/32"}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Filters.DeniedIP = []string{}
	actual.Filters.DeniedIP = []string{}
	expected.Filters.DeniedLoginMethods = []string{}
	actual.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodPublicKey}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Filters.DeniedLoginMethods = []string{}
	actual.Filters.DeniedLoginMethods = []string{}
	actual.Filters.DeniedProtocols = []string{common.ProtocolFTP}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Filters.DeniedProtocols = []string{common.ProtocolWebDAV}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	expected.Filters.DeniedProtocols = []string{}
	actual.Filters.DeniedProtocols = []string{}
	expected.Filters.MaxUploadFileSize = 0
	actual.Filters.MaxUploadFileSize = 100
	err = checkUser(expected, actual)
	assert.Error(t, err)
	actual.Filters.MaxUploadFileSize = 0
	expected.Filters.FileExtensions = append(expected.Filters.FileExtensions, dataprovider.ExtensionsFilter{
		Path:              "/",
		AllowedExtensions: []string{".jpg", ".png"},
		DeniedExtensions:  []string{".zip", ".rar"},
	})
	err = checkUser(expected, actual)
	assert.Error(t, err)
	actual.Filters.FileExtensions = append(actual.Filters.FileExtensions, dataprovider.ExtensionsFilter{
		Path:              "/sub",
		AllowedExtensions: []string{".jpg", ".png"},
		DeniedExtensions:  []string{".zip", ".rar"},
	})
	err = checkUser(expected, actual)
	assert.Error(t, err)
	actual.Filters.FileExtensions[0] = dataprovider.ExtensionsFilter{
		Path:              "/",
		AllowedExtensions: []string{".jpg"},
		DeniedExtensions:  []string{".zip", ".rar"},
	}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	actual.Filters.FileExtensions[0] = dataprovider.ExtensionsFilter{
		Path:              "/",
		AllowedExtensions: []string{".tiff", ".png"},
		DeniedExtensions:  []string{".zip", ".rar"},
	}
	err = checkUser(expected, actual)
	assert.Error(t, err)
	actual.Filters.FileExtensions[0] = dataprovider.ExtensionsFilter{
		Path:              "/",
		AllowedExtensions: []string{".jpg", ".png"},
		DeniedExtensions:  []string{".tar.gz", ".rar"},
	}
	err = checkUser(expected, actual)
	assert.Error(t, err)
}

func TestCompareUserFields(t *testing.T) {
	expected := &dataprovider.User{}
	actual := &dataprovider.User{}
	expected.Permissions = make(map[string][]string)
	actual.Permissions = make(map[string][]string)
	expected.Username = "test"
	err := compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.Username = ""
	expected.HomeDir = "homedir"
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.HomeDir = ""
	expected.UID = 1
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.UID = 0
	expected.GID = 1
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.GID = 0
	expected.MaxSessions = 2
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.MaxSessions = 0
	expected.QuotaSize = 4096
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.QuotaSize = 0
	expected.QuotaFiles = 2
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.QuotaFiles = 0
	expected.Permissions["/"] = []string{dataprovider.PermCreateDirs}
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.Permissions = nil
	expected.UploadBandwidth = 64
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.UploadBandwidth = 0
	expected.DownloadBandwidth = 128
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.DownloadBandwidth = 0
	expected.Status = 1
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
	expected.Status = 0
	expected.ExpirationDate = 123
	err = compareEqualsUserFields(expected, actual)
	assert.Error(t, err)
}

func TestCompareUserFsConfig(t *testing.T) {
	expected := &dataprovider.User{}
	actual := &dataprovider.User{}
	expected.FsConfig.Provider = dataprovider.S3FilesystemProvider
	err := compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.Provider = dataprovider.LocalFilesystemProvider
	expected.FsConfig.S3Config.Bucket = "bucket"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.Bucket = ""
	expected.FsConfig.S3Config.Region = "region"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.Region = ""
	expected.FsConfig.S3Config.AccessKey = "access key"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.AccessKey = ""
	actual.FsConfig.S3Config.AccessSecret = "access secret"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	secret, _ := utils.EncryptData("access secret")
	actual.FsConfig.S3Config.AccessSecret = ""
	expected.FsConfig.S3Config.AccessSecret = secret
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.AccessSecret = utils.RemoveDecryptionKey(secret)
	actual.FsConfig.S3Config.AccessSecret = utils.RemoveDecryptionKey(secret) + "a"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.AccessSecret = "test"
	actual.FsConfig.S3Config.AccessSecret = ""
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.AccessSecret = ""
	actual.FsConfig.S3Config.AccessSecret = ""
	expected.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.Endpoint = ""
	expected.FsConfig.S3Config.StorageClass = "Standard"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.StorageClass = ""
	expected.FsConfig.S3Config.KeyPrefix = "somedir/subdir"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.KeyPrefix = ""
	expected.FsConfig.S3Config.UploadPartSize = 10
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.S3Config.UploadPartSize = 0
	expected.FsConfig.S3Config.UploadConcurrency = 3
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
}

func TestCompareUserGCSConfig(t *testing.T) {
	expected := &dataprovider.User{}
	actual := &dataprovider.User{}
	expected.FsConfig.GCSConfig.KeyPrefix = "somedir/subdir"
	err := compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.GCSConfig.KeyPrefix = ""
	expected.FsConfig.GCSConfig.Bucket = "bucket"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.GCSConfig.Bucket = ""
	expected.FsConfig.GCSConfig.StorageClass = "Standard"
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.GCSConfig.StorageClass = ""
	expected.FsConfig.GCSConfig.AutomaticCredentials = 1
	err = compareUserFsConfig(expected, actual)
	assert.Error(t, err)
	expected.FsConfig.GCSConfig.AutomaticCredentials = 0
}

func TestGCSWebInvalidFormFile(t *testing.T) {
	form := make(url.Values)
	form.Set("username", "test_username")
	form.Set("fs_provider", "2")
	req, _ := http.NewRequest(http.MethodPost, webUserPath, strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	err := req.ParseForm()
	assert.NoError(t, err)
	_, err = getFsConfigFromUserPostFields(req)
	assert.EqualError(t, err, http.ErrNotMultipart.Error())
}

func TestApiCallsWithBadURL(t *testing.T) {
	oldBaseURL := httpBaseURL
	oldAuthUsername := authUsername
	oldAuthPassword := authPassword
	SetBaseURLAndCredentials(invalidURL, oldAuthUsername, oldAuthPassword)
	folder := vfs.BaseVirtualFolder{
		MappedPath: os.TempDir(),
	}
	u := dataprovider.User{}
	_, _, err := UpdateUser(u, http.StatusBadRequest, "")
	assert.Error(t, err)
	_, err = RemoveUser(u, http.StatusNotFound)
	assert.Error(t, err)
	_, err = RemoveFolder(folder, http.StatusNotFound)
	assert.Error(t, err)
	_, _, err = GetUsers(1, 0, "", http.StatusBadRequest)
	assert.Error(t, err)
	_, _, err = GetFolders(1, 0, "", http.StatusBadRequest)
	assert.Error(t, err)
	_, err = UpdateQuotaUsage(u, "", http.StatusNotFound)
	assert.Error(t, err)
	_, err = UpdateFolderQuotaUsage(folder, "", http.StatusNotFound)
	assert.Error(t, err)
	_, err = CloseConnection("non_existent_id", http.StatusNotFound)
	assert.Error(t, err)
	_, _, err = Dumpdata("backup.json", "", http.StatusBadRequest)
	assert.Error(t, err)
	_, _, err = Loaddata("/tmp/backup.json", "", "", http.StatusBadRequest)
	assert.Error(t, err)
	SetBaseURLAndCredentials(oldBaseURL, oldAuthUsername, oldAuthPassword)
}

func TestApiCallToNotListeningServer(t *testing.T) {
	oldBaseURL := httpBaseURL
	oldAuthUsername := authUsername
	oldAuthPassword := authPassword
	SetBaseURLAndCredentials(inactiveURL, oldAuthUsername, oldAuthPassword)
	u := dataprovider.User{}
	_, _, err := AddUser(u, http.StatusBadRequest)
	assert.Error(t, err)
	_, _, err = UpdateUser(u, http.StatusNotFound, "")
	assert.Error(t, err)
	_, err = RemoveUser(u, http.StatusNotFound)
	assert.Error(t, err)
	_, _, err = GetUserByID(-1, http.StatusNotFound)
	assert.Error(t, err)
	_, _, err = GetUsers(100, 0, "", http.StatusOK)
	assert.Error(t, err)
	_, err = UpdateQuotaUsage(u, "", http.StatusNotFound)
	assert.Error(t, err)
	_, _, err = GetQuotaScans(http.StatusOK)
	assert.Error(t, err)
	_, err = StartQuotaScan(u, http.StatusNotFound)
	assert.Error(t, err)
	folder := vfs.BaseVirtualFolder{
		MappedPath: os.TempDir(),
	}
	_, err = StartFolderQuotaScan(folder, http.StatusNotFound)
	assert.Error(t, err)
	_, _, err = AddFolder(folder, http.StatusOK)
	assert.Error(t, err)
	_, err = RemoveFolder(folder, http.StatusOK)
	assert.Error(t, err)
	_, _, err = GetFolders(0, 0, "", http.StatusOK)
	assert.Error(t, err)
	_, err = UpdateFolderQuotaUsage(folder, "", http.StatusNotFound)
	assert.Error(t, err)
	_, _, err = GetFoldersQuotaScans(http.StatusOK)
	assert.Error(t, err)
	_, _, err = GetConnections(http.StatusOK)
	assert.Error(t, err)
	_, err = CloseConnection("non_existent_id", http.StatusNotFound)
	assert.Error(t, err)
	_, _, err = GetVersion(http.StatusOK)
	assert.Error(t, err)
	_, _, err = GetProviderStatus(http.StatusOK)
	assert.Error(t, err)
	_, _, err = Dumpdata("backup.json", "0", http.StatusOK)
	assert.Error(t, err)
	_, _, err = Loaddata("/tmp/backup.json", "", "", http.StatusOK)
	assert.Error(t, err)
	SetBaseURLAndCredentials(oldBaseURL, oldAuthUsername, oldAuthPassword)
}

func TestBasicAuth(t *testing.T) {
	oldAuthUsername := authUsername
	oldAuthPassword := authPassword
	authUserFile := filepath.Join(os.TempDir(), "http_users.txt")
	authUserData := []byte("test1:$2y$05$bcHSED7aO1cfLto6ZdDBOOKzlwftslVhtpIkRhAtSa4GuLmk5mola\n")
	err := ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
	assert.NoError(t, err)
	httpAuth, _ = newBasicAuthProvider(authUserFile)
	_, _, err = GetVersion(http.StatusUnauthorized)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, "test1", "password1")
	_, _, err = GetVersion(http.StatusOK)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, "test1", "wrong_password")
	resp, _ := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(metricsPath), nil, "")
	defer resp.Body.Close()
	assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
	authUserData = append(authUserData, []byte("test2:$1$OtSSTL8b$bmaCqEksI1e7rnZSjsIDR1\n")...)
	err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, "test2", "password2")
	_, _, err = GetVersion(http.StatusOK)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, "test2", "wrong_password")
	_, _, err = GetVersion(http.StatusOK)
	assert.Error(t, err)
	authUserData = append(authUserData, []byte("test2:$apr1$gLnIkRIf$Xr/6aJfmIrihP4b2N2tcs/\n")...)
	err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, "test2", "password2")
	_, _, err = GetVersion(http.StatusOK)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, "test2", "wrong_password")
	_, _, err = GetVersion(http.StatusOK)
	assert.Error(t, err)
	authUserData = append(authUserData, []byte("test3:$apr1$gLnIkRIf$Xr/6$aJfmIr$ihP4b2N2tcs/\n")...)
	err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, "test3", "wrong_password")
	_, _, err = GetVersion(http.StatusUnauthorized)
	assert.NoError(t, err)
	authUserData = append(authUserData, []byte("test4:$invalid$gLnIkRIf$Xr/6$aJfmIr$ihP4b2N2tcs/\n")...)
	err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, "test3", "password2")
	_, _, err = GetVersion(http.StatusUnauthorized)
	assert.NoError(t, err)
	if runtime.GOOS != "windows" {
		authUserData = append(authUserData, []byte("test5:$apr1$gLnIkRIf$Xr/6aJfmIrihP4b2N2tcs/\n")...)
		err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
		assert.NoError(t, err)
		err = os.Chmod(authUserFile, 0001)
		assert.NoError(t, err)
		SetBaseURLAndCredentials(httpBaseURL, "test5", "password2")
		_, _, err = GetVersion(http.StatusUnauthorized)
		assert.NoError(t, err)
		err = os.Chmod(authUserFile, os.ModePerm)
		assert.NoError(t, err)
	}
	authUserData = append(authUserData, []byte("\"foo\"bar\"\r\n")...)
	err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, "test2", "password2")
	_, _, err = GetVersion(http.StatusUnauthorized)
	assert.NoError(t, err)
	err = os.Remove(authUserFile)
	assert.NoError(t, err)
	SetBaseURLAndCredentials(httpBaseURL, oldAuthUsername, oldAuthPassword)
	httpAuth, _ = newBasicAuthProvider("")
}

func TestCloseConnectionHandler(t *testing.T) {
	req, _ := http.NewRequest(http.MethodDelete, activeConnectionsPath+"/connectionID", nil)
	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("connectionID", "")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
	rr := httptest.NewRecorder()
	handleCloseConnection(rr, req)
	assert.Equal(t, http.StatusBadRequest, rr.Code)
}

func TestRenderInvalidTemplate(t *testing.T) {
	tmpl, err := template.New("test").Parse("{{.Count}}")
	if assert.NoError(t, err) {
		templates["no_match"] = tmpl
		rw := httptest.NewRecorder()
		renderTemplate(rw, "no_match", map[string]string{})
		assert.Equal(t, http.StatusInternalServerError, rw.Code)
	}
}

func TestQuotaScanInvalidFs(t *testing.T) {
	user := dataprovider.User{
		Username: "test",
		HomeDir:  os.TempDir(),
		FsConfig: dataprovider.Filesystem{
			Provider: dataprovider.S3FilesystemProvider,
		},
	}
	common.QuotaScans.AddUserQuotaScan(user.Username)
	err := doQuotaScan(user)
	assert.Error(t, err)
}
