From 8306b6bde62b103041cf7e0735ef3ad8d01f3455 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sun, 7 Jun 2020 23:30:18 +0200 Subject: [PATCH] refactor virtual folders The same virtual folder can now be shared among users and different folder quota limits for each user are supported. Fixes #120 --- .github/workflows/quality.yml | 2 +- README.md | 6 +- config/config.go | 2 +- dataprovider/bolt.go | 497 ++++- dataprovider/dataprovider.go | 199 +- dataprovider/memory.go | 324 ++- dataprovider/mysql.go | 98 +- dataprovider/pgsql.go | 99 +- dataprovider/sqlcommon.go | 626 +++++- dataprovider/sqlite.go | 125 +- dataprovider/sqlqueries.go | 139 +- dataprovider/user.go | 46 +- docker/sftpgo/alpine/README.md | 2 +- docs/account.md | 6 +- docs/full-configuration.md | 6 +- docs/google-cloud-storage.md | 2 +- docs/s3.md | 2 +- examples/rest-api-cli/README.md | 101 +- examples/rest-api-cli/sftpgo_api_cli.py | 76 +- httpd/api_folder.go | 104 + httpd/api_maintenance.go | 105 +- httpd/api_quota.go | 53 +- httpd/api_user.go | 4 +- httpd/api_utils.go | 191 +- httpd/httpd.go | 4 + httpd/httpd_test.go | 759 ++++++- httpd/internal_test.go | 62 +- httpd/router.go | 8 + httpd/schema/openapi.yaml | 425 +++- httpd/web.go | 209 +- sftpd/handler.go | 438 +++- sftpd/internal_test.go | 85 +- sftpd/scp.go | 69 +- sftpd/sftpd.go | 85 +- sftpd/sftpd_test.go | 1944 ++++++++++++++++- sftpd/transfer.go | 54 +- sftpgo.json | 2 +- .../vendor/fontawesome-free/css/all.min.css | 4 +- static/vendor/fontawesome-free/package.json | 81 - .../fontawesome-free/svgs/solid/folder.svg | 1 + .../webfonts/fa-solid-900.eot | Bin 192122 -> 202902 bytes .../webfonts/fa-solid-900.svg | 547 +++-- .../webfonts/fa-solid-900.ttf | Bin 191836 -> 202616 bytes .../webfonts/fa-solid-900.woff | Bin 98016 -> 103300 bytes .../webfonts/fa-solid-900.woff2 | Bin 75408 -> 79444 bytes static/vendor/jquery/jquery.min.js | 4 +- static/vendor/jquery/jquery.slim.min.js | 2 - templates/base.html | 6 + templates/folder.html | 23 + templates/folders.html | 205 ++ templates/user.html | 4 +- vfs/folder.go | 71 + vfs/gcsfs.go | 6 + vfs/osfs.go | 46 +- vfs/s3fs.go | 6 + vfs/vfs.go | 22 +- 56 files changed, 6969 insertions(+), 1018 deletions(-) create mode 100644 httpd/api_folder.go delete mode 100644 static/vendor/fontawesome-free/package.json create mode 100644 static/vendor/fontawesome-free/svgs/solid/folder.svg delete mode 100644 static/vendor/jquery/jquery.slim.min.js create mode 100644 templates/folder.html create mode 100644 templates/folders.html create mode 100644 vfs/folder.go diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 5781711c..d6ca2526 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v2 - name: Install golangci-lint - run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.26.0 + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.27.0 - name: Run golangci-lint run: golangci-lint run diff --git a/README.md b/README.md index d21638ab..6328d63e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Fully featured and highly configurable SFTP server, written in Go - Per user IP filters are supported: login can be restricted to specific ranges of IP addresses or to a specific IP address. - Per user and per directory file extensions filters are supported: files can be allowed or denied based on their extensions. - Virtual folders are supported: directories outside the user home directory can be exposed as virtual folders. -- Configurable custom commands and/or HTTP notifications on file upload, download, delete, rename, on SSH commands and on user add, update and delete. +- Configurable custom commands and/or HTTP notifications on file upload, download, pre-delete, delete, rename, on SSH commands and on user add, update and delete. - Automatically terminating idle connections. - Atomic uploads are configurable. - Support for Git repositories over SSH. @@ -132,6 +132,10 @@ SFTPGo allows to configure custom commands and/or HTTP notifications on file upl More information about custom actions can be found [here](./docs/custom-actions.md). +## Virtual folders + +Directories outside the user home directory can be exposed as virtual folders, more information [here](./docs/virtual-folders.md). + ## Storage backends ### S3 Compabible Object Storage backends diff --git a/config/config.go b/config/config.go index 67a89d4d..20148ca7 100644 --- a/config/config.go +++ b/config/config.go @@ -75,7 +75,7 @@ func init() { Username: "", Password: "", ConnectionString: "", - UsersTable: "users", + SQLTablesPrefix: "", ManageUsers: 1, SSLMode: 0, TrackQuota: 1, diff --git a/dataprovider/bolt.go b/dataprovider/bolt.go index 8097c4aa..483be3ef 100644 --- a/dataprovider/bolt.go +++ b/dataprovider/bolt.go @@ -14,15 +14,17 @@ import ( "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/utils" + "github.com/drakkan/sftpgo/vfs" ) const ( - boltDatabaseVersion = 3 + boltDatabaseVersion = 4 ) var ( usersBucket = []byte("users") usersIDIdxBucket = []byte("users_id_idx") + foldersBucket = []byte("folders") dbVersionBucket = []byte("db_version") dbVersionKey = []byte("version") ) @@ -90,6 +92,14 @@ func initializeBoltProvider(basePath string) error { providerLog(logger.LevelWarn, "error creating username idx bucket: %v", err) return err } + err = dbHandle.Update(func(tx *bolt.Tx) error { + _, e := tx.CreateBucketIfNotExists(foldersBucket) + return e + }) + if err != nil { + providerLog(logger.LevelWarn, "error creating username idx bucket: %v", err) + return err + } err = dbHandle.Update(func(tx *bolt.Tx) error { _, e := tx.CreateBucketIfNotExists(dbVersionBucket) return e @@ -106,7 +116,7 @@ func initializeBoltProvider(basePath string) error { } func (p BoltProvider) checkAvailability() error { - _, err := p.getUsers(1, 0, "ASC", "") + _, err := getBoltDatabaseVersion(p.dbHandle) return err } @@ -152,7 +162,12 @@ func (p BoltProvider) getUserByID(ID int64) (User, error) { if u == nil { return &RecordNotFoundError{err: fmt.Sprintf("username %#v and ID: %v does not exist", string(username), ID)} } - return json.Unmarshal(u, &user) + folderBucket, err := getFolderBucket(tx) + if err != nil { + return err + } + user, err = joinUserAndFolders(u, folderBucket) + return err }) return user, err @@ -215,7 +230,10 @@ func (p BoltProvider) updateQuota(username string, filesAdd int, sizeAdd int64, if err != nil { return err } - return bucket.Put([]byte(username), buf) + err = bucket.Put([]byte(username), buf) + providerLog(logger.LevelDebug, "quota updated for user %#v, files increment: %v size increment: %v is reset? %v", + username, filesAdd, sizeAdd, reset) + return err }) } @@ -239,7 +257,12 @@ func (p BoltProvider) userExists(username string) (User, error) { if u == nil { return &RecordNotFoundError{err: fmt.Sprintf("username %v does not exist", username)} } - return json.Unmarshal(u, &user) + folderBucket, err := getFolderBucket(tx) + if err != nil { + return err + } + user, err = joinUserAndFolders(u, folderBucket) + return err }) return user, err } @@ -254,6 +277,10 @@ func (p BoltProvider) addUser(user User) error { if err != nil { return err } + folderBucket, err := getFolderBucket(tx) + if err != nil { + return err + } if u := bucket.Get([]byte(user.Username)); u != nil { return fmt.Errorf("username %v already exists", user.Username) } @@ -262,15 +289,21 @@ func (p BoltProvider) addUser(user User) error { return err } user.ID = int64(id) + for _, folder := range user.VirtualFolders { + err = addUserToFolderMapping(folder, user, folderBucket) + if err != nil { + return err + } + } buf, err := json.Marshal(user) if err != nil { return err } - userIDAsBytes := itob(user.ID) err = bucket.Put([]byte(user.Username), buf) if err != nil { return err } + userIDAsBytes := itob(user.ID) return idxBucket.Put(userIDAsBytes, []byte(user.Username)) }) } @@ -285,9 +318,35 @@ func (p BoltProvider) updateUser(user User) error { if err != nil { return err } - if u := bucket.Get([]byte(user.Username)); u == nil { + folderBucket, err := getFolderBucket(tx) + if err != nil { + return err + } + var u []byte + if u = bucket.Get([]byte(user.Username)); u == nil { return &RecordNotFoundError{err: fmt.Sprintf("username %v does not exist", user.Username)} } + var oldUser User + err = json.Unmarshal(u, &oldUser) + if err != nil { + return err + } + for _, folder := range oldUser.VirtualFolders { + err = removeUserFromFolderMapping(folder, oldUser, folderBucket) + if err != nil { + return err + } + } + for _, folder := range user.VirtualFolders { + err = addUserToFolderMapping(folder, user, folderBucket) + if err != nil { + return err + } + } + user.LastQuotaUpdate = oldUser.LastQuotaUpdate + user.UsedQuotaSize = oldUser.UsedQuotaSize + user.UsedQuotaFiles = oldUser.UsedQuotaFiles + user.LastLogin = oldUser.LastLogin buf, err := json.Marshal(user) if err != nil { return err @@ -302,6 +361,18 @@ func (p BoltProvider) deleteUser(user User) error { if err != nil { return err } + if len(user.VirtualFolders) > 0 { + folderBucket, err := getFolderBucket(tx) + if err != nil { + return err + } + for _, folder := range user.VirtualFolders { + err = removeUserFromFolderMapping(folder, user, folderBucket) + if err != nil { + return err + } + } + } userIDAsBytes := itob(user.ID) userName := idxBucket.Get(userIDAsBytes) if userName == nil { @@ -316,16 +387,19 @@ func (p BoltProvider) deleteUser(user User) error { } func (p BoltProvider) dumpUsers() ([]User, error) { - users := []User{} + users := make([]User, 0, 100) err := p.dbHandle.View(func(tx *bolt.Tx) error { bucket, _, err := getBuckets(tx) if err != nil { return err } + folderBucket, err := getFolderBucket(tx) + if err != nil { + return err + } cursor := bucket.Cursor() for k, v := cursor.First(); k != nil; k, v = cursor.Next() { - var user User - err = json.Unmarshal(v, &user) + user, err := joinUserAndFolders(v, folderBucket) if err != nil { return err } @@ -355,7 +429,7 @@ func (p BoltProvider) getUserWithUsername(username string) ([]User, error) { } func (p BoltProvider) getUsers(limit int, offset int, order string, username string) ([]User, error) { - users := []User{} + users := make([]User, 0, limit) var err error if limit <= 0 { return users, err @@ -371,16 +445,19 @@ func (p BoltProvider) getUsers(limit int, offset int, order string, username str if err != nil { return err } + folderBucket, err := getFolderBucket(tx) + if err != nil { + return err + } cursor := bucket.Cursor() itNum := 0 - if order == "ASC" { + if order == OrderASC { for k, v := cursor.First(); k != nil; k, v = cursor.Next() { itNum++ if itNum <= offset { continue } - var user User - err = json.Unmarshal(v, &user) + user, err := joinUserAndFolders(v, folderBucket) if err == nil { users = append(users, HideUserSensitiveData(&user)) } @@ -394,8 +471,7 @@ func (p BoltProvider) getUsers(limit int, offset int, order string, username str if itNum <= offset { continue } - var user User - err = json.Unmarshal(v, &user) + user, err := joinUserAndFolders(v, folderBucket) if err == nil { users = append(users, HideUserSensitiveData(&user)) } @@ -409,6 +485,209 @@ func (p BoltProvider) getUsers(limit int, offset int, order string, username str return users, err } +func (p BoltProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) { + folders := make([]vfs.BaseVirtualFolder, 0, 50) + err := p.dbHandle.View(func(tx *bolt.Tx) error { + bucket, err := getFolderBucket(tx) + if err != nil { + return err + } + cursor := bucket.Cursor() + for k, v := cursor.First(); k != nil; k, v = cursor.Next() { + var folder vfs.BaseVirtualFolder + err = json.Unmarshal(v, &folder) + if err != nil { + return err + } + folders = append(folders, folder) + } + return err + }) + return folders, err +} + +func (p BoltProvider) getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error) { + folders := make([]vfs.BaseVirtualFolder, 0, limit) + var err error + if limit <= 0 { + return folders, err + } + if len(folderPath) > 0 { + if offset == 0 { + var folder vfs.BaseVirtualFolder + folder, err = p.getFolderByPath(folderPath) + if err == nil { + folders = append(folders, folder) + } + } + return folders, err + } + err = p.dbHandle.View(func(tx *bolt.Tx) error { + bucket, err := getFolderBucket(tx) + if err != nil { + return err + } + cursor := bucket.Cursor() + itNum := 0 + if order == OrderASC { + for k, v := cursor.First(); k != nil; k, v = cursor.Next() { + itNum++ + if itNum <= offset { + continue + } + var folder vfs.BaseVirtualFolder + err = json.Unmarshal(v, &folder) + if err != nil { + return err + } + folders = append(folders, folder) + if len(folders) >= limit { + break + } + } + } else { + for k, v := cursor.Last(); k != nil; k, v = cursor.Prev() { + itNum++ + if itNum <= offset { + continue + } + var folder vfs.BaseVirtualFolder + err = json.Unmarshal(v, &folder) + if err != nil { + return err + } + folders = append(folders, folder) + if len(folders) >= limit { + break + } + } + } + return err + }) + return folders, err +} + +func (p BoltProvider) getFolderByPath(name string) (vfs.BaseVirtualFolder, error) { + var folder vfs.BaseVirtualFolder + err := p.dbHandle.View(func(tx *bolt.Tx) error { + bucket, err := getFolderBucket(tx) + if err != nil { + return err + } + folder, err = folderExistsInternal(name, bucket) + return err + }) + return folder, err +} + +func (p BoltProvider) addFolder(folder vfs.BaseVirtualFolder) error { + err := validateFolder(&folder) + if err != nil { + return err + } + return p.dbHandle.Update(func(tx *bolt.Tx) error { + bucket, err := getFolderBucket(tx) + if err != nil { + return err + } + if f := bucket.Get([]byte(folder.MappedPath)); f != nil { + return fmt.Errorf("folder %v already exists", folder.MappedPath) + } + _, err = addFolderInternal(folder, bucket) + return err + }) +} + +func (p BoltProvider) deleteFolder(folder vfs.BaseVirtualFolder) error { + return p.dbHandle.Update(func(tx *bolt.Tx) error { + bucket, err := getFolderBucket(tx) + if err != nil { + return err + } + usersBucket, _, err := getBuckets(tx) + if err != nil { + return err + } + var f []byte + if f = bucket.Get([]byte(folder.MappedPath)); f == nil { + return &RecordNotFoundError{err: fmt.Sprintf("folder %v does not exist", folder.MappedPath)} + } + var folder vfs.BaseVirtualFolder + err = json.Unmarshal(f, &folder) + if err != nil { + return err + } + for _, username := range folder.Users { + var u []byte + if u = usersBucket.Get([]byte(username)); u == nil { + continue + } + var user User + err = json.Unmarshal(u, &user) + if err != nil { + return err + } + var folders []vfs.VirtualFolder + for _, userFolder := range user.VirtualFolders { + if folder.MappedPath != userFolder.MappedPath { + folders = append(folders, userFolder) + } + } + user.VirtualFolders = folders + buf, err := json.Marshal(user) + if err != nil { + return err + } + err = usersBucket.Put([]byte(user.Username), buf) + if err != nil { + return err + } + } + + return bucket.Delete([]byte(folder.MappedPath)) + }) +} + +func (p BoltProvider) updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error { + return p.dbHandle.Update(func(tx *bolt.Tx) error { + bucket, err := getFolderBucket(tx) + if err != nil { + return err + } + var f []byte + if f = bucket.Get([]byte(mappedPath)); f == nil { + return &RecordNotFoundError{err: fmt.Sprintf("folder %v does not exist, unable to update quota", mappedPath)} + } + var folder vfs.BaseVirtualFolder + err = json.Unmarshal(f, &folder) + if err != nil { + return err + } + if reset { + folder.UsedQuotaSize = sizeAdd + folder.UsedQuotaFiles = filesAdd + } else { + folder.UsedQuotaSize += sizeAdd + folder.UsedQuotaFiles += filesAdd + } + folder.LastQuotaUpdate = utils.GetTimeAsMsSinceEpoch(time.Now()) + buf, err := json.Marshal(folder) + if err != nil { + return err + } + return bucket.Put([]byte(folder.MappedPath), buf) + }) +} + +func (p BoltProvider) getUsedFolderQuota(mappedPath string) (int, int64, error) { + folder, err := p.getFolderByPath(mappedPath) + if err != nil { + providerLog(logger.LevelWarn, "unable to get quota for folder %#v error: %v", mappedPath, err) + return 0, 0, err + } + return folder.UsedQuotaFiles, folder.UsedQuotaSize, err +} + func (p BoltProvider) close() error { return p.dbHandle.Close() } @@ -437,9 +716,19 @@ func (p BoltProvider) migrateDatabase() error { if err != nil { return err } - return updateDatabaseFrom2To3(p.dbHandle) + err = updateDatabaseFrom2To3(p.dbHandle) + if err != nil { + return err + } + return updateDatabaseFrom3To4(p.dbHandle) case 2: - return updateDatabaseFrom2To3(p.dbHandle) + err = updateDatabaseFrom2To3(p.dbHandle) + if err != nil { + return err + } + return updateDatabaseFrom3To4(p.dbHandle) + case 3: + return updateDatabaseFrom3To4(p.dbHandle) default: return fmt.Errorf("Database version not handled: %v", dbVersion.Version) } @@ -452,6 +741,106 @@ func itob(v int64) []byte { return b } +func joinUserAndFolders(u []byte, foldersBucket *bolt.Bucket) (User, error) { + var user User + err := json.Unmarshal(u, &user) + if len(user.VirtualFolders) > 0 { + var folders []vfs.VirtualFolder + for _, folder := range user.VirtualFolders { + baseFolder, err := folderExistsInternal(folder.MappedPath, foldersBucket) + if err != nil { + continue + } + folder.UsedQuotaFiles = baseFolder.UsedQuotaFiles + folder.UsedQuotaSize = baseFolder.UsedQuotaSize + folder.LastQuotaUpdate = baseFolder.LastQuotaUpdate + folder.ID = baseFolder.ID + folders = append(folders, folder) + } + user.VirtualFolders = folders + } + return user, err +} + +func folderExistsInternal(name string, bucket *bolt.Bucket) (vfs.BaseVirtualFolder, error) { + var folder vfs.BaseVirtualFolder + f := bucket.Get([]byte(name)) + if f == nil { + err := &RecordNotFoundError{err: fmt.Sprintf("folder %v does not exist", name)} + return folder, err + } + err := json.Unmarshal(f, &folder) + return folder, err +} + +func addFolderInternal(folder vfs.BaseVirtualFolder, bucket *bolt.Bucket) (vfs.BaseVirtualFolder, error) { + id, err := bucket.NextSequence() + if err != nil { + return folder, err + } + folder.ID = int64(id) + buf, err := json.Marshal(folder) + if err != nil { + return folder, err + } + err = bucket.Put([]byte(folder.MappedPath), buf) + return folder, err +} + +func addUserToFolderMapping(folder vfs.VirtualFolder, user User, bucket *bolt.Bucket) error { + var baseFolder vfs.BaseVirtualFolder + var err error + if f := bucket.Get([]byte(folder.MappedPath)); f == nil { + // folder does not exists, try to create + baseFolder, err = addFolderInternal(folder.BaseVirtualFolder, bucket) + } else { + err = json.Unmarshal(f, &baseFolder) + } + if err != nil { + return err + } + if !utils.IsStringInSlice(user.Username, baseFolder.Users) { + baseFolder.Users = append(baseFolder.Users, user.Username) + buf, err := json.Marshal(baseFolder) + if err != nil { + return err + } + err = bucket.Put([]byte(folder.MappedPath), buf) + if err != nil { + return err + } + } + return err +} + +func removeUserFromFolderMapping(folder vfs.VirtualFolder, user User, bucket *bolt.Bucket) error { + var f []byte + if f = bucket.Get([]byte(folder.MappedPath)); f == nil { + // the folder does not exists so there is no associated user + return nil + } + var baseFolder vfs.BaseVirtualFolder + err := json.Unmarshal(f, &baseFolder) + if err != nil { + return err + } + if utils.IsStringInSlice(user.Username, baseFolder.Users) { + var newUserMapping []string + for _, u := range baseFolder.Users { + if u != user.Username { + newUserMapping = append(newUserMapping, u) + } + } + baseFolder.Users = newUserMapping + buf, err := json.Marshal(baseFolder) + if err != nil { + return err + } + return bucket.Put([]byte(folder.MappedPath), buf) + } + return err +} + func getBuckets(tx *bolt.Tx) (*bolt.Bucket, *bolt.Bucket, error) { var err error bucket := tx.Bucket(usersBucket) @@ -462,6 +851,15 @@ func getBuckets(tx *bolt.Tx) (*bolt.Bucket, *bolt.Bucket, error) { return bucket, idxBucket, err } +func getFolderBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + var err error + bucket := tx.Bucket(foldersBucket) + if bucket == nil { + err = fmt.Errorf("unable to find required buckets, bolt database structure not correcly defined") + } + return bucket, err +} + func updateDatabaseFrom1To2(dbHandle *bolt.DB) error { providerLog(logger.LevelInfo, "updating bolt database version: 1 -> 2") usernames, err := getBoltAvailableUsernames(dbHandle) @@ -537,6 +935,69 @@ func updateDatabaseFrom2To3(dbHandle *bolt.DB) error { return updateBoltDatabaseVersion(dbHandle, 3) } +func updateDatabaseFrom3To4(dbHandle *bolt.DB) error { + providerLog(logger.LevelInfo, "updating bolt database version: 3 -> 4") + foldersToScan := []string{} + users := []userCompactVFolders{} + err := dbHandle.View(func(tx *bolt.Tx) error { + bucket, _, err := getBuckets(tx) + if err != nil { + return err + } + cursor := bucket.Cursor() + for k, v := cursor.First(); k != nil; k, v = cursor.Next() { + var compatUser userCompactVFolders + err = json.Unmarshal(v, &compatUser) + if err == nil && len(compatUser.VirtualFolders) > 0 { + users = append(users, compatUser) + } + } + return err + }) + if err != nil { + return err + } + for _, u := range users { + user, err := provider.userExists(u.Username) + if err != nil { + return err + } + var folders []vfs.VirtualFolder + for _, f := range u.VirtualFolders { + providerLog(logger.LevelInfo, "restoring virtual folder: %+v for user %#v", f, user.Username) + quotaSize := int64(-1) + quotaFiles := -1 + if f.ExcludeFromQuota { + quotaSize = 0 + quotaFiles = 0 + } + folder := vfs.VirtualFolder{ + QuotaSize: quotaSize, + QuotaFiles: quotaFiles, + VirtualPath: f.VirtualPath, + } + folder.MappedPath = f.MappedPath + folders = append(folders, folder) + if !utils.IsStringInSlice(folder.MappedPath, foldersToScan) { + foldersToScan = append(foldersToScan, folder.MappedPath) + } + } + user.VirtualFolders = folders + err = provider.updateUser(user) + providerLog(logger.LevelInfo, "number of virtual folders to restore %v, user %#v, error: %v", len(user.VirtualFolders), + user.Username, err) + if err != nil { + return err + } + } + + err = updateBoltDatabaseVersion(dbHandle, 4) + if err == nil { + go updateVFoldersQuotaAfterRestore(foldersToScan) + } + return err +} + func getBoltAvailableUsernames(dbHandle *bolt.DB) ([]string, error) { usernames := []string{} err := dbHandle.View(func(tx *bolt.Tx) error { diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index b4031ad8..d3be252c 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -1,6 +1,5 @@ // Package dataprovider provides data access. -// It abstract different data providers and exposes a common API. -// Currently the supported data providers are: PostreSQL (9+), MySQL (4.1+) and SQLite 3.x +// It abstracts different data providers and exposes a common API. package dataprovider import ( @@ -72,6 +71,13 @@ const ( operationAdd = "add" operationUpdate = "update" operationDelete = "delete" + sqlPrefixValidChars = "abcdefghijklmnopqrstuvwxyz_" +) + +// ordering constants +const ( + OrderASC = "ASC" + OrderDESC = "DESC" ) var ( @@ -100,6 +106,10 @@ var ( errWrongPassword = errors.New("password does not match") errNoInitRequired = errors.New("initialization is not required for this data provider") credentialsDirPath string + sqlTableUsers = "users" + sqlTableFolders = "folders" + sqlTableFoldersMapping = "folders_mapping" + sqlTableSchemaVersion = "schema_version" ) type schemaVersion struct { @@ -143,14 +153,15 @@ type Config struct { // Custom database connection string. // If not empty this connection string will be used instead of build one using the previous parameters ConnectionString string `json:"connection_string" mapstructure:"connection_string"` - // Database table for SFTP users - UsersTable string `json:"users_table" mapstructure:"users_table"` + // prefix for SQL tables + SQLTablesPrefix string `json:"sql_tables_prefix" mapstructure:"sql_tables_prefix"` // Set to 0 to disable users management, 1 to enable ManageUsers int `json:"manage_users" mapstructure:"manage_users"` // Set the preferred way to track users quota between the following choices: // 0, disable quota tracking. REST API to scan user dir and update quota will do nothing // 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions - // 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions. + // 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions + // and for virtual folders. // With this configuration the "quota scan" REST API can still be used to periodically update space usage // for users without quota restrictions TrackQuota int `json:"track_quota" mapstructure:"track_quota"` @@ -253,7 +264,8 @@ type Config struct { // BackupData defines the structure for the backup/restore files type BackupData struct { - Users []User `json:"users"` + Users []User `json:"users"` + Folders []vfs.BaseVirtualFolder `json:"folders"` } type keyboardAuthHookRequest struct { @@ -272,6 +284,18 @@ type keyboardAuthHookResponse struct { CheckPwd int `json:"check_password"` } +type virtualFoldersCompact struct { + VirtualPath string `json:"virtual_path"` + MappedPath string `json:"mapped_path"` + ExcludeFromQuota bool `json:"exclude_from_quota"` +} + +type userCompactVFolders struct { + ID int64 `json:"id"` + Username string `json:"username"` + VirtualFolders []virtualFoldersCompact `json:"virtual_folders"` +} + // ValidationError raised if input data is not valid type ValidationError struct { err string @@ -313,7 +337,7 @@ func GetQuotaTracking() int { return config.TrackQuota } -// Provider interface that data providers must implement. +// Provider defines the interface that data providers must implement. type Provider interface { validateUserAndPass(username string, password string) (User, error) validateUserAndPubKey(username string, pubKey []byte) (User, string, error) @@ -327,6 +351,13 @@ type Provider interface { dumpUsers() ([]User, error) getUserByID(ID int64) (User, error) updateLastLogin(username string) error + getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error) + getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error) + addFolder(folder vfs.BaseVirtualFolder) error + deleteFolder(folder vfs.BaseVirtualFolder) error + updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error + getUsedFolderQuota(mappedPath string) (int, int64, error) + dumpFolders() ([]vfs.BaseVirtualFolder, error) checkAvailability() error close() error reloadConfig() error @@ -343,7 +374,6 @@ func init() { func Initialize(cnf Config, basePath string) error { var err error config = cnf - sqlPlaceholders = getSQLPlaceholders() if err = validateHooks(); err != nil { return err @@ -388,10 +418,26 @@ func validateHooks() error { return nil } +func validateSQLTablesPrefix() error { + if len(config.SQLTablesPrefix) > 0 { + for _, char := range config.SQLTablesPrefix { + if !strings.Contains(sqlPrefixValidChars, strings.ToLower(string(char))) { + return errors.New("Invalid sql_tables_prefix only chars in range 'a..z', 'A..Z' and '_' are allowed") + } + } + sqlTableUsers = config.SQLTablesPrefix + sqlTableUsers + sqlTableFolders = config.SQLTablesPrefix + sqlTableFolders + sqlTableFoldersMapping = config.SQLTablesPrefix + sqlTableFoldersMapping + sqlTableSchemaVersion = config.SQLTablesPrefix + sqlTableSchemaVersion + providerLog(logger.LevelDebug, "sql table for users %#v, folders %#v folders mapping %#v schema version %#v", + sqlTableUsers, sqlTableFolders, sqlTableFoldersMapping, sqlTableSchemaVersion) + } + return nil +} + // InitializeDatabase creates the initial database structure func InitializeDatabase(cnf Config, basePath string) error { config = cnf - sqlPlaceholders = getSQLPlaceholders() if config.Driver == BoltDataProviderName || config.Driver == MemoryDataProviderName { return errNoInitRequired @@ -481,8 +527,19 @@ func UpdateUserQuota(p Provider, user User, filesAdd int, sizeAdd int64, reset b return p.updateQuota(user.Username, filesAdd, sizeAdd, reset) } +// UpdateVirtualFolderQuota updates the quota for the given virtual folder adding filesAdd and sizeAdd. +// If reset is true filesAdd and sizeAdd indicates the total files and the total size instead of the difference. +func UpdateVirtualFolderQuota(p Provider, vfolder vfs.BaseVirtualFolder, filesAdd int, sizeAdd int64, reset bool) error { + if config.TrackQuota == 0 { + return &MethodDisabledError{err: trackQuotaDisabledError} + } + if config.ManageUsers == 0 { + return &MethodDisabledError{err: manageUsersDisabledError} + } + return p.updateFolderQuota(vfolder.MappedPath, filesAdd, sizeAdd, reset) +} + // GetUsedQuota returns the used quota for the given SFTP user. -// TrackQuota must be >=1 to enable this method func GetUsedQuota(p Provider, username string) (int, int64, error) { if config.TrackQuota == 0 { return 0, 0, &MethodDisabledError{err: trackQuotaDisabledError} @@ -490,6 +547,14 @@ func GetUsedQuota(p Provider, username string) (int, int64, error) { return p.getUsedQuota(username) } +// GetUsedVirtualFolderQuota returns the used quota for the given virtual folder. +func GetUsedVirtualFolderQuota(p Provider, mappedPath string) (int, int64, error) { + if config.TrackQuota == 0 { + return 0, 0, &MethodDisabledError{err: trackQuotaDisabledError} + } + return p.getUsedFolderQuota(mappedPath) +} + // UserExists checks if the given SFTP username exists, returns an error if no match is found func UserExists(p Provider, username string) (User, error) { return p.userExists(username) @@ -534,11 +599,6 @@ func DeleteUser(p Provider, user User) error { return err } -// DumpUsers returns an array with all users including their hashed password -func DumpUsers(p Provider) ([]User, error) { - return p.dumpUsers() -} - // ReloadConfig reloads provider configuration. // Currently only implemented for memory provider, allows to reload the users // from the configured file, if defined @@ -547,7 +607,7 @@ func ReloadConfig() error { } // GetUsers returns an array of users respecting limit and offset and filtered by username exact match if not empty -func GetUsers(p Provider, limit int, offset int, order string, username string) ([]User, error) { +func GetUsers(p Provider, limit, offset int, order string, username string) ([]User, error) { return p.getUsers(limit, offset, order, username) } @@ -556,6 +616,50 @@ func GetUserByID(p Provider, ID int64) (User, error) { return p.getUserByID(ID) } +// AddFolder adds a new virtual folder. +// ManageUsers configuration must be set to 1 to enable this method +func AddFolder(p Provider, folder vfs.BaseVirtualFolder) error { + if config.ManageUsers == 0 { + return &MethodDisabledError{err: manageUsersDisabledError} + } + return p.addFolder(folder) +} + +// DeleteFolder deletes an existing folder. +// ManageUsers configuration must be set to 1 to enable this method +func DeleteFolder(p Provider, folder vfs.BaseVirtualFolder) error { + if config.ManageUsers == 0 { + return &MethodDisabledError{err: manageUsersDisabledError} + } + return p.deleteFolder(folder) +} + +// GetFolderByPath returns the folder with the specified path if any +func GetFolderByPath(p Provider, mappedPath string) (vfs.BaseVirtualFolder, error) { + return p.getFolderByPath(mappedPath) +} + +// GetFolders returns an array of folders respecting limit and offset +func GetFolders(p Provider, limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error) { + return p.getFolders(limit, offset, order, folderPath) +} + +// DumpData returns all users and folders +func DumpData(p Provider) (BackupData, error) { + var data BackupData + users, err := p.dumpUsers() + if err != nil { + return data, err + } + folders, err := p.dumpFolders() + if err != nil { + return data, err + } + data.Users = users + data.Folders = folders + return data, err +} + // GetProviderStatus returns an error if the provider is not available func GetProviderStatus(p Provider) error { return p.checkAvailability() @@ -572,6 +676,10 @@ func Close(p Provider) error { func createProvider(basePath string) error { var err error + sqlPlaceholders = getSQLPlaceholders() + if err = validateSQLTablesPrefix(); err != nil { + return err + } if config.Driver == SQLiteDataProviderName { err = initializeSQLiteProvider(basePath) } else if config.Driver == PGSQLDataProviderName { @@ -630,7 +738,21 @@ func isMappedDirOverlapped(dir1, dir2 string) bool { return false } -func validateVirtualFolders(user *User) error { +func validateFolderQuotaLimits(folder vfs.VirtualFolder) error { + if folder.QuotaSize < -1 { + return &ValidationError{err: fmt.Sprintf("invalid quota_size: %v folder path %#v", folder.QuotaSize, folder.MappedPath)} + } + if folder.QuotaFiles < -1 { + return &ValidationError{err: fmt.Sprintf("invalid quota_file: %v folder path %#v", folder.QuotaSize, folder.MappedPath)} + } + if (folder.QuotaSize == -1 && folder.QuotaFiles != -1) || (folder.QuotaFiles == -1 && folder.QuotaSize != -1) { + return &ValidationError{err: fmt.Sprintf("virtual folder quota_size and quota_files must be both -1 or >= 0, quota_size: %v quota_files: %v", + folder.QuotaFiles, folder.QuotaSize)} + } + return nil +} + +func validateUserVirtualFolders(user *User) error { if len(user.VirtualFolders) == 0 || user.FsConfig.Provider != 0 { user.VirtualFolders = []vfs.VirtualFolder{} return nil @@ -642,6 +764,9 @@ func validateVirtualFolders(user *User) error { if !path.IsAbs(cleanedVPath) || cleanedVPath == "/" { return &ValidationError{err: fmt.Sprintf("invalid virtual folder %#v", v.VirtualPath)} } + if err := validateFolderQuotaLimits(v); err != nil { + return err + } cleanedMPath := filepath.Clean(v.MappedPath) if !filepath.IsAbs(cleanedMPath) { return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v", v.MappedPath)} @@ -651,9 +776,12 @@ func validateVirtualFolders(user *User) error { v.MappedPath, user.GetHomeDir())} } virtualFolders = append(virtualFolders, vfs.VirtualFolder{ - VirtualPath: cleanedVPath, - MappedPath: cleanedMPath, - ExcludeFromQuota: v.ExcludeFromQuota, + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: cleanedMPath, + }, + VirtualPath: cleanedVPath, + QuotaSize: v.QuotaSize, + QuotaFiles: v.QuotaFiles, }) for k, virtual := range mappedPaths { if isMappedDirOverlapped(k, cleanedMPath) { @@ -859,6 +987,15 @@ func createUserPasswordHash(user *User) error { return nil } +func validateFolder(folder *vfs.BaseVirtualFolder) error { + cleanedMPath := filepath.Clean(folder.MappedPath) + if !filepath.IsAbs(cleanedMPath) { + return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v", folder.MappedPath)} + } + folder.MappedPath = cleanedMPath + return nil +} + func validateUser(user *User) error { buildUserHomeDir(user) if err := validateBaseParams(user); err != nil { @@ -870,7 +1007,7 @@ func validateUser(user *User) error { if err := validateFilesystemConfig(user); err != nil { return err } - if err := validateVirtualFolders(user); err != nil { + if err := validateUserVirtualFolders(user); err != nil { return err } if user.Status < 0 || user.Status > 1 { @@ -1581,3 +1718,23 @@ func executeAction(operation string, user User) { executeNotificationCommand(operation, user) //nolint:errcheck // the error is used in test cases only } } + +// after migrating database to v4 we have to update the quota for the imported folders +func updateVFoldersQuotaAfterRestore(foldersToScan []string) { + fs := vfs.NewOsFs("", "", nil).(vfs.OsFs) + for _, folder := range foldersToScan { + providerLog(logger.LevelDebug, "starting quota scan after migration for folder %#v", folder) + vfolder, err := provider.getFolderByPath(folder) + if err != nil { + providerLog(logger.LevelWarn, "error getting folder to scan %#v: %v", folder, err) + continue + } + numFiles, size, err := fs.GetDirSize(folder) + if err != nil { + providerLog(logger.LevelWarn, "error scanning folder %#v: %v", folder, err) + continue + } + err = UpdateVirtualFolderQuota(provider, vfolder, numFiles, size, true) + providerLog(logger.LevelDebug, "quota updated for virtual folder %#v, error: %v", vfolder.MappedPath, err) + } +} diff --git a/dataprovider/memory.go b/dataprovider/memory.go index 41b7b0c1..d825793c 100644 --- a/dataprovider/memory.go +++ b/dataprovider/memory.go @@ -13,6 +13,7 @@ import ( "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/utils" + "github.com/drakkan/sftpgo/vfs" ) var ( @@ -27,6 +28,10 @@ type memoryProviderHandle struct { usersIdx map[int64]string // map for users, username is the key users map[string]User + // map for virtual folders, MappedPath is the key + vfolders map[string]vfs.BaseVirtualFolder + // slice with ordered folders mapped path + vfoldersPaths []string // configuration file to use for loading users configFile string lock *sync.Mutex @@ -48,12 +53,14 @@ func initializeMemoryProvider(basePath string) error { } provider = MemoryProvider{ dbHandle: &memoryProviderHandle{ - isClosed: false, - usernames: []string{}, - usersIdx: make(map[int64]string), - users: make(map[string]User), - configFile: configFile, - lock: new(sync.Mutex), + isClosed: false, + usernames: []string{}, + usersIdx: make(map[int64]string), + users: make(map[string]User), + vfolders: make(map[string]vfs.BaseVirtualFolder), + vfoldersPaths: []string{}, + configFile: configFile, + lock: new(sync.Mutex), }, } return provider.reloadConfig() @@ -85,7 +92,7 @@ func (p MemoryProvider) validateUserAndPass(username string, password string) (U } user, err := p.userExists(username) if err != nil { - providerLog(logger.LevelWarn, "error authenticating user: %v, error: %v", username, err) + providerLog(logger.LevelWarn, "error authenticating user %#v, error: %v", username, err) return user, err } return checkUserAndPass(user, password) @@ -98,7 +105,7 @@ func (p MemoryProvider) validateUserAndPubKey(username string, pubKey []byte) (U } user, err := p.userExists(username) if err != nil { - providerLog(logger.LevelWarn, "error authenticating user: %v, error: %v", username, err) + providerLog(logger.LevelWarn, "error authenticating user %#v, error: %v", username, err) return user, "", err } return checkUserAndPubKey(user, pubKey) @@ -139,7 +146,7 @@ func (p MemoryProvider) updateQuota(username string, filesAdd int, sizeAdd int64 } user, err := p.userExistsInternal(username) if err != nil { - providerLog(logger.LevelWarn, "unable to update quota for user %v error: %v", username, err) + providerLog(logger.LevelWarn, "unable to update quota for user %#v error: %v", username, err) return err } if reset { @@ -150,6 +157,8 @@ func (p MemoryProvider) updateQuota(username string, filesAdd int, sizeAdd int64 user.UsedQuotaFiles += filesAdd } user.LastQuotaUpdate = utils.GetTimeAsMsSinceEpoch(time.Now()) + providerLog(logger.LevelDebug, "quota updated for user %#v, files increment: %v size increment: %v is reset? %v", + username, filesAdd, sizeAdd, reset) p.dbHandle.users[user.Username] = user return nil } @@ -162,7 +171,7 @@ func (p MemoryProvider) getUsedQuota(username string) (int, int64, error) { } user, err := p.userExistsInternal(username) if err != nil { - providerLog(logger.LevelWarn, "unable to get quota for user %v error: %v", username, err) + providerLog(logger.LevelWarn, "unable to get quota for user %#v error: %v", username, err) return 0, 0, err } return user.UsedQuotaFiles, user.UsedQuotaSize, err @@ -180,9 +189,10 @@ func (p MemoryProvider) addUser(user User) error { } _, err = p.userExistsInternal(user.Username) if err == nil { - return fmt.Errorf("username %v already exists", user.Username) + return fmt.Errorf("username %#v already exists", user.Username) } user.ID = p.getNextID() + user.VirtualFolders = p.joinVirtualFoldersFields(user) p.dbHandle.users[user.Username] = user p.dbHandle.usersIdx[user.ID] = user.Username p.dbHandle.usernames = append(p.dbHandle.usernames, user.Username) @@ -200,10 +210,18 @@ func (p MemoryProvider) updateUser(user User) error { if err != nil { return err } - _, err = p.userExistsInternal(user.Username) + u, err := p.userExistsInternal(user.Username) if err != nil { return err } + for _, oldFolder := range u.VirtualFolders { + p.removeUserFromFolderMapping(oldFolder.MappedPath, u.Username) + } + user.VirtualFolders = p.joinVirtualFoldersFields(user) + user.LastQuotaUpdate = u.LastQuotaUpdate + user.UsedQuotaSize = u.UsedQuotaSize + user.UsedQuotaFiles = u.UsedQuotaFiles + user.LastLogin = u.LastLogin p.dbHandle.users[user.Username] = user return nil } @@ -214,10 +232,13 @@ func (p MemoryProvider) deleteUser(user User) error { if p.dbHandle.isClosed { return errMemoryProviderClosed } - _, err := p.userExistsInternal(user.Username) + u, err := p.userExistsInternal(user.Username) if err != nil { return err } + for _, oldFolder := range u.VirtualFolders { + p.removeUserFromFolderMapping(oldFolder.MappedPath, u.Username) + } delete(p.dbHandle.users, user.Username) delete(p.dbHandle.usersIdx, user.ID) // this could be more efficient @@ -230,10 +251,10 @@ func (p MemoryProvider) deleteUser(user User) error { } func (p MemoryProvider) dumpUsers() ([]User, error) { - users := []User{} - var err error p.dbHandle.lock.Lock() defer p.dbHandle.lock.Unlock() + users := make([]User, 0, len(p.dbHandle.usernames)) + var err error if p.dbHandle.isClosed { return users, errMemoryProviderClosed } @@ -248,8 +269,21 @@ func (p MemoryProvider) dumpUsers() ([]User, error) { return users, err } +func (p MemoryProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) { + p.dbHandle.lock.Lock() + defer p.dbHandle.lock.Unlock() + folders := make([]vfs.BaseVirtualFolder, 0, len(p.dbHandle.vfoldersPaths)) + if p.dbHandle.isClosed { + return folders, errMemoryProviderClosed + } + for _, f := range p.dbHandle.vfolders { + folders = append(folders, f) + } + return folders, nil +} + func (p MemoryProvider) getUsers(limit int, offset int, order string, username string) ([]User, error) { - users := []User{} + users := make([]User, 0, limit) var err error p.dbHandle.lock.Lock() defer p.dbHandle.lock.Unlock() @@ -269,7 +303,7 @@ func (p MemoryProvider) getUsers(limit int, offset int, order string, username s return users, err } itNum := 0 - if order == "ASC" { + if order == OrderASC { for _, username := range p.dbHandle.usernames { itNum++ if itNum <= offset { @@ -311,7 +345,224 @@ func (p MemoryProvider) userExistsInternal(username string) (User, error) { if val, ok := p.dbHandle.users[username]; ok { return val.getACopy(), nil } - return User{}, &RecordNotFoundError{err: fmt.Sprintf("username %v does not exist", username)} + return User{}, &RecordNotFoundError{err: fmt.Sprintf("username %#v does not exist", username)} +} + +func (p MemoryProvider) updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error { + p.dbHandle.lock.Lock() + defer p.dbHandle.lock.Unlock() + if p.dbHandle.isClosed { + return errMemoryProviderClosed + } + folder, err := p.folderExistsInternal(mappedPath) + if err != nil { + providerLog(logger.LevelWarn, "unable to update quota for folder %#v error: %v", mappedPath, err) + return err + } + if reset { + folder.UsedQuotaSize = sizeAdd + folder.UsedQuotaFiles = filesAdd + } else { + folder.UsedQuotaSize += sizeAdd + folder.UsedQuotaFiles += filesAdd + } + folder.LastQuotaUpdate = utils.GetTimeAsMsSinceEpoch(time.Now()) + p.dbHandle.vfolders[mappedPath] = folder + return nil +} + +func (p MemoryProvider) getUsedFolderQuota(mappedPath string) (int, int64, error) { + p.dbHandle.lock.Lock() + defer p.dbHandle.lock.Unlock() + if p.dbHandle.isClosed { + return 0, 0, errMemoryProviderClosed + } + folder, err := p.folderExistsInternal(mappedPath) + if err != nil { + providerLog(logger.LevelWarn, "unable to get quota for folder %#v error: %v", mappedPath, err) + return 0, 0, err + } + return folder.UsedQuotaFiles, folder.UsedQuotaSize, err +} + +func (p MemoryProvider) joinVirtualFoldersFields(user User) []vfs.VirtualFolder { + var folders []vfs.VirtualFolder + for _, folder := range user.VirtualFolders { + f, err := p.addOrGetFolderInternal(folder.MappedPath, user.Username, folder.UsedQuotaSize, folder.UsedQuotaFiles, + folder.LastQuotaUpdate) + if err == nil { + folder.UsedQuotaFiles = f.UsedQuotaFiles + folder.UsedQuotaSize = f.UsedQuotaSize + folder.LastQuotaUpdate = f.LastQuotaUpdate + folder.ID = f.ID + folders = append(folders, folder) + } + } + return folders +} + +func (p MemoryProvider) removeUserFromFolderMapping(mappedPath, username string) { + folder, err := p.folderExistsInternal(mappedPath) + if err == nil { + var usernames []string + for _, user := range folder.Users { + if user != username { + usernames = append(usernames, user) + } + } + folder.Users = usernames + p.dbHandle.vfolders[folder.MappedPath] = folder + } +} + +func (p MemoryProvider) updateFoldersMappingInternal(folder vfs.BaseVirtualFolder) { + p.dbHandle.vfolders[folder.MappedPath] = folder + if !utils.IsStringInSlice(folder.MappedPath, p.dbHandle.vfoldersPaths) { + p.dbHandle.vfoldersPaths = append(p.dbHandle.vfoldersPaths, folder.MappedPath) + sort.Strings(p.dbHandle.vfoldersPaths) + } +} + +func (p MemoryProvider) addOrGetFolderInternal(mappedPath, username string, usedQuotaSize int64, usedQuotaFiles int, lastQuotaUpdate int64) (vfs.BaseVirtualFolder, error) { + folder, err := p.folderExistsInternal(mappedPath) + if _, ok := err.(*RecordNotFoundError); ok { + folder := vfs.BaseVirtualFolder{ + ID: p.getNextFolderID(), + MappedPath: mappedPath, + UsedQuotaSize: usedQuotaSize, + UsedQuotaFiles: usedQuotaFiles, + LastQuotaUpdate: lastQuotaUpdate, + Users: []string{username}, + } + p.updateFoldersMappingInternal(folder) + return folder, nil + } + if err == nil && !utils.IsStringInSlice(username, folder.Users) { + folder.Users = append(folder.Users, username) + p.updateFoldersMappingInternal(folder) + } + return folder, err +} + +func (p MemoryProvider) folderExistsInternal(mappedPath string) (vfs.BaseVirtualFolder, error) { + if val, ok := p.dbHandle.vfolders[mappedPath]; ok { + return val, nil + } + return vfs.BaseVirtualFolder{}, &RecordNotFoundError{err: fmt.Sprintf("folder %#v does not exist", mappedPath)} +} + +func (p MemoryProvider) getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error) { + folders := make([]vfs.BaseVirtualFolder, 0, limit) + var err error + p.dbHandle.lock.Lock() + defer p.dbHandle.lock.Unlock() + if p.dbHandle.isClosed { + return folders, errMemoryProviderClosed + } + if limit <= 0 { + return folders, err + } + if len(folderPath) > 0 { + if offset == 0 { + var folder vfs.BaseVirtualFolder + folder, err = p.folderExistsInternal(folderPath) + if err == nil { + folders = append(folders, folder) + } + } + return folders, err + } + itNum := 0 + if order == OrderASC { + for _, mappedPath := range p.dbHandle.vfoldersPaths { + itNum++ + if itNum <= offset { + continue + } + folder := p.dbHandle.vfolders[mappedPath] + folders = append(folders, folder) + if len(folders) >= limit { + break + } + } + } else { + for i := len(p.dbHandle.vfoldersPaths) - 1; i >= 0; i-- { + itNum++ + if itNum <= offset { + continue + } + mappedPath := p.dbHandle.vfoldersPaths[i] + folder := p.dbHandle.vfolders[mappedPath] + folders = append(folders, folder) + if len(folders) >= limit { + break + } + } + } + return folders, err +} + +func (p MemoryProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error) { + p.dbHandle.lock.Lock() + defer p.dbHandle.lock.Unlock() + if p.dbHandle.isClosed { + return vfs.BaseVirtualFolder{}, errMemoryProviderClosed + } + return p.folderExistsInternal(mappedPath) +} + +func (p MemoryProvider) addFolder(folder vfs.BaseVirtualFolder) error { + p.dbHandle.lock.Lock() + defer p.dbHandle.lock.Unlock() + if p.dbHandle.isClosed { + return errMemoryProviderClosed + } + err := validateFolder(&folder) + if err != nil { + return err + } + _, err = p.folderExistsInternal(folder.MappedPath) + if err == nil { + return fmt.Errorf("folder %#v already exists", folder.MappedPath) + } + folder.ID = p.getNextFolderID() + p.dbHandle.vfolders[folder.MappedPath] = folder + p.dbHandle.vfoldersPaths = append(p.dbHandle.vfoldersPaths, folder.MappedPath) + sort.Strings(p.dbHandle.vfoldersPaths) + return nil +} + +func (p MemoryProvider) deleteFolder(folder vfs.BaseVirtualFolder) error { + p.dbHandle.lock.Lock() + defer p.dbHandle.lock.Unlock() + if p.dbHandle.isClosed { + return errMemoryProviderClosed + } + + _, err := p.folderExistsInternal(folder.MappedPath) + if err != nil { + return err + } + for _, username := range folder.Users { + user, err := p.userExistsInternal(username) + if err == nil { + var folders []vfs.VirtualFolder + for _, userFolder := range user.VirtualFolders { + if folder.MappedPath != userFolder.MappedPath { + folders = append(folders, userFolder) + } + } + user.VirtualFolders = folders + p.dbHandle.users[user.Username] = user + } + } + delete(p.dbHandle.vfolders, folder.MappedPath) + p.dbHandle.vfoldersPaths = []string{} + for mappedPath := range p.dbHandle.vfolders { + p.dbHandle.vfoldersPaths = append(p.dbHandle.vfoldersPaths, mappedPath) + } + sort.Strings(p.dbHandle.vfoldersPaths) + return nil } func (p MemoryProvider) getNextID() int64 { @@ -324,12 +575,24 @@ func (p MemoryProvider) getNextID() int64 { return nextID } -func (p MemoryProvider) clearUsers() { +func (p MemoryProvider) getNextFolderID() int64 { + nextID := int64(1) + for _, v := range p.dbHandle.vfolders { + if v.ID >= nextID { + nextID = v.ID + 1 + } + } + return nextID +} + +func (p MemoryProvider) clear() { p.dbHandle.lock.Lock() defer p.dbHandle.lock.Unlock() p.dbHandle.usernames = []string{} p.dbHandle.usersIdx = make(map[int64]string) p.dbHandle.users = make(map[string]User) + p.dbHandle.vfoldersPaths = []string{} + p.dbHandle.vfolders = make(map[string]vfs.BaseVirtualFolder) } func (p MemoryProvider) reloadConfig() error { @@ -364,23 +627,30 @@ func (p MemoryProvider) reloadConfig() error { providerLog(logger.LevelWarn, "error loading users: %v", err) return err } - p.clearUsers() + p.clear() + for _, folder := range dump.Folders { + _, err := p.getFolderByPath(folder.MappedPath) + if err == nil { + logger.Debug(logSender, "", "folder %#v already exists, restore not needed", folder.MappedPath) + continue + } + folder.Users = nil + err = p.addFolder(folder) + if err != nil { + providerLog(logger.LevelWarn, "error adding folder %#v: %v", folder.MappedPath, err) + return err + } + } for _, user := range dump.Users { u, err := p.userExists(user.Username) if err == nil { user.ID = u.ID - user.LastLogin = u.LastLogin - user.UsedQuotaSize = u.UsedQuotaSize - user.UsedQuotaFiles = u.UsedQuotaFiles err = p.updateUser(user) if err != nil { providerLog(logger.LevelWarn, "error updating user %#v: %v", user.Username, err) return err } } else { - user.LastLogin = 0 - user.UsedQuotaSize = 0 - user.UsedQuotaFiles = 0 err = p.addUser(user) if err != nil { providerLog(logger.LevelWarn, "error adding user %#v: %v", user.Username, err) @@ -388,7 +658,7 @@ func (p MemoryProvider) reloadConfig() error { } } } - providerLog(logger.LevelDebug, "users loaded from file: %#v", p.dbHandle.configFile) + providerLog(logger.LevelDebug, "user and folders loaded from file: %#v", p.dbHandle.configFile) return nil } diff --git a/dataprovider/mysql.go b/dataprovider/mysql.go index edfb2bf1..80f211ea 100644 --- a/dataprovider/mysql.go +++ b/dataprovider/mysql.go @@ -13,6 +13,7 @@ import ( "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/utils" + "github.com/drakkan/sftpgo/vfs" ) const ( @@ -24,9 +25,18 @@ const ( "`upload_bandwidth` integer NOT NULL, `download_bandwidth` integer NOT NULL, `expiration_date` bigint(20) NOT NULL, " + "`last_login` bigint(20) NOT NULL, `status` int(11) NOT NULL, `filters` longtext DEFAULT NULL, " + "`filesystem` longtext DEFAULT NULL);" - mysqlSchemaTableSQL = "CREATE TABLE `schema_version` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `version` integer NOT NULL);" - mysqlUsersV2SQL = "ALTER TABLE `{{users}}` ADD COLUMN `virtual_folders` longtext NULL;" - mysqlUsersV3SQL = "ALTER TABLE `{{users}}` MODIFY `password` longtext NULL;" + mysqlSchemaTableSQL = "CREATE TABLE `{{schema_version}}` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `version` integer NOT NULL);" + mysqlV2SQL = "ALTER TABLE `{{users}}` ADD COLUMN `virtual_folders` longtext NULL;" + mysqlV3SQL = "ALTER TABLE `{{users}}` MODIFY `password` longtext NULL;" + mysqlV4SQL = "CREATE TABLE `{{folders}}` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `path` varchar(512) NOT NULL UNIQUE," + + "`used_quota_size` bigint NOT NULL, `used_quota_files` integer NOT NULL, `last_quota_update` bigint NOT NULL);" + + "ALTER TABLE `{{users}}` MODIFY `home_dir` varchar(512) NOT NULL;" + + "ALTER TABLE `{{users}}` DROP COLUMN `virtual_folders`;" + + "CREATE TABLE `{{folders_mapping}}` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `virtual_path` varchar(512) NOT NULL, " + + "`quota_size` bigint NOT NULL, `quota_files` integer NOT NULL, `folder_id` integer NOT NULL, `user_id` integer NOT NULL);" + + "ALTER TABLE `{{folders_mapping}}` ADD CONSTRAINT `unique_mapping` UNIQUE (`user_id`, `folder_id`);" + + "ALTER TABLE `{{folders_mapping}}` ADD CONSTRAINT `folders_mapping_folder_id_fk_folders_id` FOREIGN KEY (`folder_id`) REFERENCES `{{folders}}` (`id`) ON DELETE CASCADE;" + + "ALTER TABLE `{{folders_mapping}}` ADD CONSTRAINT `folders_mapping_user_id_fk_users_id` FOREIGN KEY (`user_id`) REFERENCES `{{users}}` (`id`) ON DELETE CASCADE;" ) // MySQLProvider auth provider for MySQL/MariaDB database @@ -89,14 +99,14 @@ func (p MySQLProvider) updateQuota(username string, filesAdd int, sizeAdd int64, return sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle) } -func (p MySQLProvider) updateLastLogin(username string) error { - return sqlCommonUpdateLastLogin(username, p.dbHandle) -} - func (p MySQLProvider) getUsedQuota(username string) (int, int64, error) { return sqlCommonGetUsedQuota(username, p.dbHandle) } +func (p MySQLProvider) updateLastLogin(username string) error { + return sqlCommonUpdateLastLogin(username, p.dbHandle) +} + func (p MySQLProvider) userExists(username string) (User, error) { return sqlCommonCheckUserExists(username, p.dbHandle) } @@ -121,6 +131,34 @@ func (p MySQLProvider) getUsers(limit int, offset int, order string, username st return sqlCommonGetUsers(limit, offset, order, username, p.dbHandle) } +func (p MySQLProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) { + return sqlCommonDumpFolders(p.dbHandle) +} + +func (p MySQLProvider) getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error) { + return sqlCommonGetFolders(limit, offset, order, folderPath, p.dbHandle) +} + +func (p MySQLProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error) { + return sqlCommonCheckFolderExists(mappedPath, p.dbHandle) +} + +func (p MySQLProvider) addFolder(folder vfs.BaseVirtualFolder) error { + return sqlCommonAddFolder(folder, p.dbHandle) +} + +func (p MySQLProvider) deleteFolder(folder vfs.BaseVirtualFolder) error { + return sqlCommonDeleteFolder(folder, p.dbHandle) +} + +func (p MySQLProvider) updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error { + return sqlCommonUpdateFolderQuota(mappedPath, filesAdd, sizeAdd, reset, p.dbHandle) +} + +func (p MySQLProvider) getUsedFolderQuota(mappedPath string) (int, int64, error) { + return sqlCommonGetFolderUsedQuota(mappedPath, p.dbHandle) +} + func (p MySQLProvider) close() error { return p.dbHandle.Close() } @@ -131,7 +169,7 @@ func (p MySQLProvider) reloadConfig() error { // initializeDatabase creates the initial database structure func (p MySQLProvider) initializeDatabase() error { - sqlUsers := strings.Replace(mysqlUsersTableSQL, "{{users}}", config.UsersTable, 1) + sqlUsers := strings.Replace(mysqlUsersTableSQL, "{{users}}", sqlTableUsers, 1) tx, err := p.dbHandle.Begin() if err != nil { return err @@ -141,12 +179,12 @@ func (p MySQLProvider) initializeDatabase() error { sqlCommonRollbackTransaction(tx) return err } - _, err = tx.Exec(mysqlSchemaTableSQL) + _, err = tx.Exec(strings.Replace(mysqlSchemaTableSQL, "{{schema_version}}", sqlTableSchemaVersion, 1)) if err != nil { sqlCommonRollbackTransaction(tx) return err } - _, err = tx.Exec(initialDBVersionSQL) + _, err = tx.Exec(strings.Replace(initialDBVersionSQL, "{{schema_version}}", sqlTableSchemaVersion, 1)) if err != nil { sqlCommonRollbackTransaction(tx) return err @@ -169,9 +207,19 @@ func (p MySQLProvider) migrateDatabase() error { if err != nil { return err } - return updateMySQLDatabaseFrom2To3(p.dbHandle) + err = updateMySQLDatabaseFrom2To3(p.dbHandle) + if err != nil { + return err + } + return updateMySQLDatabaseFrom3To4(p.dbHandle) case 2: - return updateMySQLDatabaseFrom2To3(p.dbHandle) + err = updateMySQLDatabaseFrom2To3(p.dbHandle) + if err != nil { + return err + } + return updateMySQLDatabaseFrom3To4(p.dbHandle) + case 3: + return updateMySQLDatabaseFrom3To4(p.dbHandle) default: return fmt.Errorf("Database version not handled: %v", dbVersion.Version) } @@ -179,30 +227,16 @@ func (p MySQLProvider) migrateDatabase() error { func updateMySQLDatabaseFrom1To2(dbHandle *sql.DB) error { providerLog(logger.LevelInfo, "updating database version: 1 -> 2") - sql := strings.Replace(mysqlUsersV2SQL, "{{users}}", config.UsersTable, 1) - return updateMySQLDatabase(dbHandle, sql, 2) + sql := strings.Replace(mysqlV2SQL, "{{users}}", sqlTableUsers, 1) + return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2) } func updateMySQLDatabaseFrom2To3(dbHandle *sql.DB) error { providerLog(logger.LevelInfo, "updating database version: 2 -> 3") - sql := strings.Replace(mysqlUsersV3SQL, "{{users}}", config.UsersTable, 1) - return updateMySQLDatabase(dbHandle, sql, 3) + sql := strings.Replace(mysqlV3SQL, "{{users}}", sqlTableUsers, 1) + return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3) } -func updateMySQLDatabase(dbHandle *sql.DB, sql string, newVersion int) error { - tx, err := dbHandle.Begin() - if err != nil { - return err - } - _, err = tx.Exec(sql) - if err != nil { - sqlCommonRollbackTransaction(tx) - return err - } - err = sqlCommonUpdateDatabaseVersionWithTX(tx, newVersion) - if err != nil { - sqlCommonRollbackTransaction(tx) - return err - } - return tx.Commit() +func updateMySQLDatabaseFrom3To4(dbHandle *sql.DB) error { + return sqlCommonUpdateDatabaseFrom3To4(mysqlV4SQL, dbHandle) } diff --git a/dataprovider/pgsql.go b/dataprovider/pgsql.go index b5ef445e..d8c2ba29 100644 --- a/dataprovider/pgsql.go +++ b/dataprovider/pgsql.go @@ -12,6 +12,7 @@ import ( "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/utils" + "github.com/drakkan/sftpgo/vfs" ) const ( @@ -22,9 +23,19 @@ const ( "last_quota_update" bigint NOT NULL, "upload_bandwidth" integer NOT NULL, "download_bandwidth" integer NOT NULL, "expiration_date" bigint NOT NULL, "last_login" bigint NOT NULL, "status" integer NOT NULL, "filters" text NULL, "filesystem" text NULL);` - pgsqlSchemaTableSQL = `CREATE TABLE "schema_version" ("id" serial NOT NULL PRIMARY KEY, "version" integer NOT NULL);` - pgsqlUsersV2SQL = `ALTER TABLE "{{users}}" ADD COLUMN "virtual_folders" text NULL;` - pgsqlUsersV3SQL = `ALTER TABLE "{{users}}" ALTER COLUMN "password" TYPE text USING "password"::text;` + pgsqlSchemaTableSQL = `CREATE TABLE "{{schema_version}}" ("id" serial NOT NULL PRIMARY KEY, "version" integer NOT NULL);` + pgsqlV2SQL = `ALTER TABLE "{{users}}" ADD COLUMN "virtual_folders" text NULL;` + pgsqlV3SQL = `ALTER TABLE "{{users}}" ALTER COLUMN "password" TYPE text USING "password"::text;` + pgsqlV4SQL = `CREATE TABLE "{{folders}}" ("id" serial NOT NULL PRIMARY KEY, "path" varchar(512) NOT NULL UNIQUE, "used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL); +ALTER TABLE "{{users}}" ALTER COLUMN "home_dir" TYPE varchar(512) USING "home_dir"::varchar(512); +ALTER TABLE "{{users}}" DROP COLUMN "virtual_folders" CASCADE; +CREATE TABLE "{{folders_mapping}}" ("id" serial NOT NULL PRIMARY KEY, "virtual_path" varchar(512) NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "folder_id" integer NOT NULL, "user_id" integer NOT NULL); +ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "unique_mapping" UNIQUE ("user_id", "folder_id"); +ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "folders_mapping_folder_id_fk_folders_id" FOREIGN KEY ("folder_id") REFERENCES "{{folders}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "folders_mapping_user_id_fk_users_id" FOREIGN KEY ("user_id") REFERENCES "{{users}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +CREATE INDEX "folders_mapping_folder_id_idx" ON "{{folders_mapping}}" ("folder_id"); +CREATE INDEX "folders_mapping_user_id_idx" ON "{{folders_mapping}}" ("user_id"); +` ) // PGSQLProvider auth provider for PostgreSQL database @@ -87,14 +98,14 @@ func (p PGSQLProvider) updateQuota(username string, filesAdd int, sizeAdd int64, return sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle) } -func (p PGSQLProvider) updateLastLogin(username string) error { - return sqlCommonUpdateLastLogin(username, p.dbHandle) -} - func (p PGSQLProvider) getUsedQuota(username string) (int, int64, error) { return sqlCommonGetUsedQuota(username, p.dbHandle) } +func (p PGSQLProvider) updateLastLogin(username string) error { + return sqlCommonUpdateLastLogin(username, p.dbHandle) +} + func (p PGSQLProvider) userExists(username string) (User, error) { return sqlCommonCheckUserExists(username, p.dbHandle) } @@ -119,6 +130,34 @@ func (p PGSQLProvider) getUsers(limit int, offset int, order string, username st return sqlCommonGetUsers(limit, offset, order, username, p.dbHandle) } +func (p PGSQLProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) { + return sqlCommonDumpFolders(p.dbHandle) +} + +func (p PGSQLProvider) getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error) { + return sqlCommonGetFolders(limit, offset, order, folderPath, p.dbHandle) +} + +func (p PGSQLProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error) { + return sqlCommonCheckFolderExists(mappedPath, p.dbHandle) +} + +func (p PGSQLProvider) addFolder(folder vfs.BaseVirtualFolder) error { + return sqlCommonAddFolder(folder, p.dbHandle) +} + +func (p PGSQLProvider) deleteFolder(folder vfs.BaseVirtualFolder) error { + return sqlCommonDeleteFolder(folder, p.dbHandle) +} + +func (p PGSQLProvider) updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error { + return sqlCommonUpdateFolderQuota(mappedPath, filesAdd, sizeAdd, reset, p.dbHandle) +} + +func (p PGSQLProvider) getUsedFolderQuota(mappedPath string) (int, int64, error) { + return sqlCommonGetFolderUsedQuota(mappedPath, p.dbHandle) +} + func (p PGSQLProvider) close() error { return p.dbHandle.Close() } @@ -129,7 +168,7 @@ func (p PGSQLProvider) reloadConfig() error { // initializeDatabase creates the initial database structure func (p PGSQLProvider) initializeDatabase() error { - sqlUsers := strings.Replace(pgsqlUsersTableSQL, "{{users}}", config.UsersTable, 1) + sqlUsers := strings.Replace(pgsqlUsersTableSQL, "{{users}}", sqlTableUsers, 1) tx, err := p.dbHandle.Begin() if err != nil { return err @@ -139,12 +178,12 @@ func (p PGSQLProvider) initializeDatabase() error { sqlCommonRollbackTransaction(tx) return err } - _, err = tx.Exec(pgsqlSchemaTableSQL) + _, err = tx.Exec(strings.Replace(pgsqlSchemaTableSQL, "{{schema_version}}", sqlTableSchemaVersion, 1)) if err != nil { sqlCommonRollbackTransaction(tx) return err } - _, err = tx.Exec(initialDBVersionSQL) + _, err = tx.Exec(strings.Replace(initialDBVersionSQL, "{{schema_version}}", sqlTableSchemaVersion, 1)) if err != nil { sqlCommonRollbackTransaction(tx) return err @@ -167,9 +206,19 @@ func (p PGSQLProvider) migrateDatabase() error { if err != nil { return err } - return updatePGSQLDatabaseFrom2To3(p.dbHandle) + err = updatePGSQLDatabaseFrom2To3(p.dbHandle) + if err != nil { + return err + } + return updatePGSQLDatabaseFrom3To4(p.dbHandle) case 2: - return updatePGSQLDatabaseFrom2To3(p.dbHandle) + err = updatePGSQLDatabaseFrom2To3(p.dbHandle) + if err != nil { + return err + } + return updatePGSQLDatabaseFrom3To4(p.dbHandle) + case 3: + return updatePGSQLDatabaseFrom3To4(p.dbHandle) default: return fmt.Errorf("Database version not handled: %v", dbVersion.Version) } @@ -177,30 +226,16 @@ func (p PGSQLProvider) migrateDatabase() error { func updatePGSQLDatabaseFrom1To2(dbHandle *sql.DB) error { providerLog(logger.LevelInfo, "updating database version: 1 -> 2") - sql := strings.Replace(pgsqlUsersV2SQL, "{{users}}", config.UsersTable, 1) - return updatePGSQLDatabase(dbHandle, sql, 2) + sql := strings.Replace(pgsqlV2SQL, "{{users}}", sqlTableUsers, 1) + return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2) } func updatePGSQLDatabaseFrom2To3(dbHandle *sql.DB) error { providerLog(logger.LevelInfo, "updating database version: 2 -> 3") - sql := strings.Replace(pgsqlUsersV3SQL, "{{users}}", config.UsersTable, 1) - return updatePGSQLDatabase(dbHandle, sql, 3) + sql := strings.Replace(pgsqlV3SQL, "{{users}}", sqlTableUsers, 1) + return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3) } -func updatePGSQLDatabase(dbHandle *sql.DB, sql string, newVersion int) error { - tx, err := dbHandle.Begin() - if err != nil { - return err - } - _, err = tx.Exec(sql) - if err != nil { - sqlCommonRollbackTransaction(tx) - return err - } - err = sqlCommonUpdateDatabaseVersionWithTX(tx, newVersion) - if err != nil { - sqlCommonRollbackTransaction(tx) - return err - } - return tx.Commit() +func updatePGSQLDatabaseFrom3To4(dbHandle *sql.DB) error { + return sqlCommonUpdateDatabaseFrom3To4(pgsqlV4SQL, dbHandle) } diff --git a/dataprovider/sqlcommon.go b/dataprovider/sqlcommon.go index dff934a1..7edc88d4 100644 --- a/dataprovider/sqlcommon.go +++ b/dataprovider/sqlcommon.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/json" "errors" + "strings" "time" "github.com/drakkan/sftpgo/logger" @@ -13,11 +14,17 @@ import ( ) const ( - sqlDatabaseVersion = 3 - initialDBVersionSQL = "INSERT INTO schema_version (version) VALUES (1);" + sqlDatabaseVersion = 4 + initialDBVersionSQL = "INSERT INTO {{schema_version}} (version) VALUES (1);" ) -func getUserByUsername(username string, dbHandle *sql.DB) (User, error) { +var errSQLFoldersAssosaction = errors.New("unable to associate virtual folders to user") + +type sqlQuerier interface { + Prepare(query string) (*sql.Stmt, error) +} + +func getUserByUsername(username string, dbHandle sqlQuerier) (User, error) { var user User q := getUserByUsernameQuery() stmt, err := dbHandle.Prepare(q) @@ -28,7 +35,11 @@ func getUserByUsername(username string, dbHandle *sql.DB) (User, error) { defer stmt.Close() row := stmt.QueryRow(username) - return getUserFromDbRow(row, nil) + user, err = getUserFromDbRow(row, nil) + if err != nil { + return user, err + } + return getUserWithVirtualFolders(user, dbHandle) } func sqlCommonValidateUserAndPass(username string, password string, dbHandle *sql.DB) (User, error) { @@ -74,7 +85,11 @@ func sqlCommonGetUserByID(ID int64, dbHandle *sql.DB) (User, error) { defer stmt.Close() row := stmt.QueryRow(ID) - return getUserFromDbRow(row, nil) + user, err = getUserFromDbRow(row, nil) + if err != nil { + return user, err + } + return getUserWithVirtualFolders(user, dbHandle) } func sqlCommonUpdateQuota(username string, filesAdd int, sizeAdd int64, reset bool, dbHandle *sql.DB) error { @@ -95,23 +110,6 @@ func sqlCommonUpdateQuota(username string, filesAdd int, sizeAdd int64, reset bo return err } -func sqlCommonUpdateLastLogin(username string, dbHandle *sql.DB) error { - q := getUpdateLastLoginQuery() - stmt, err := dbHandle.Prepare(q) - if err != nil { - providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) - return err - } - defer stmt.Close() - _, err = stmt.Exec(utils.GetTimeAsMsSinceEpoch(time.Now()), username) - if err == nil { - providerLog(logger.LevelDebug, "last login updated for user %#v", username) - } else { - providerLog(logger.LevelWarn, "error updating last login for user %#v: %v", username, err) - } - return err -} - func sqlCommonGetUsedQuota(username string, dbHandle *sql.DB) (int, int64, error) { q := getQuotaQuery() stmt, err := dbHandle.Prepare(q) @@ -131,6 +129,23 @@ func sqlCommonGetUsedQuota(username string, dbHandle *sql.DB) (int, int64, error return usedFiles, usedSize, err } +func sqlCommonUpdateLastLogin(username string, dbHandle *sql.DB) error { + q := getUpdateLastLoginQuery() + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return err + } + defer stmt.Close() + _, err = stmt.Exec(utils.GetTimeAsMsSinceEpoch(time.Now()), username) + if err == nil { + providerLog(logger.LevelDebug, "last login updated for user %#v", username) + } else { + providerLog(logger.LevelWarn, "error updating last login for user %#v: %v", username, err) + } + return err +} + func sqlCommonCheckUserExists(username string, dbHandle *sql.DB) (User, error) { var user User q := getUserByUsernameQuery() @@ -141,7 +156,11 @@ func sqlCommonCheckUserExists(username string, dbHandle *sql.DB) (User, error) { } defer stmt.Close() row := stmt.QueryRow(username) - return getUserFromDbRow(row, nil) + user, err = getUserFromDbRow(row, nil) + if err != nil { + return user, err + } + return getUserWithVirtualFolders(user, dbHandle) } func sqlCommonAddUser(user User, dbHandle *sql.DB) error { @@ -149,37 +168,51 @@ func sqlCommonAddUser(user User, dbHandle *sql.DB) error { if err != nil { return err } + tx, err := dbHandle.Begin() + if err != nil { + return err + } q := getAddUserQuery() - stmt, err := dbHandle.Prepare(q) + stmt, err := tx.Prepare(q) if err != nil { providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + sqlCommonRollbackTransaction(tx) return err } defer stmt.Close() permissions, err := user.GetPermissionsAsJSON() if err != nil { + sqlCommonRollbackTransaction(tx) return err } publicKeys, err := user.GetPublicKeysAsJSON() if err != nil { + sqlCommonRollbackTransaction(tx) return err } filters, err := user.GetFiltersAsJSON() if err != nil { + sqlCommonRollbackTransaction(tx) return err } fsConfig, err := user.GetFsConfigAsJSON() if err != nil { - return err - } - virtualFolders, err := user.GetVirtualFoldersAsJSON() - if err != nil { + sqlCommonRollbackTransaction(tx) return err } _, err = stmt.Exec(user.Username, user.Password, string(publicKeys), user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize, user.QuotaFiles, string(permissions), user.UploadBandwidth, user.DownloadBandwidth, user.Status, user.ExpirationDate, string(filters), - string(fsConfig), string(virtualFolders)) - return err + string(fsConfig)) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + err = generateVirtualFoldersMapping(user, tx) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + return tx.Commit() } func sqlCommonUpdateUser(user User, dbHandle *sql.DB) error { @@ -187,37 +220,51 @@ func sqlCommonUpdateUser(user User, dbHandle *sql.DB) error { if err != nil { return err } + tx, err := dbHandle.Begin() + if err != nil { + return err + } q := getUpdateUserQuery() - stmt, err := dbHandle.Prepare(q) + stmt, err := tx.Prepare(q) if err != nil { providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + sqlCommonRollbackTransaction(tx) return err } defer stmt.Close() permissions, err := user.GetPermissionsAsJSON() if err != nil { + sqlCommonRollbackTransaction(tx) return err } publicKeys, err := user.GetPublicKeysAsJSON() if err != nil { + sqlCommonRollbackTransaction(tx) return err } filters, err := user.GetFiltersAsJSON() if err != nil { + sqlCommonRollbackTransaction(tx) return err } fsConfig, err := user.GetFsConfigAsJSON() if err != nil { - return err - } - virtualFolders, err := user.GetVirtualFoldersAsJSON() - if err != nil { + sqlCommonRollbackTransaction(tx) return err } _, err = stmt.Exec(user.Password, string(publicKeys), user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize, user.QuotaFiles, string(permissions), user.UploadBandwidth, user.DownloadBandwidth, user.Status, user.ExpirationDate, - string(filters), string(fsConfig), string(virtualFolders), user.ID) - return err + string(filters), string(fsConfig), user.ID) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + err = generateVirtualFoldersMapping(user, tx) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + return tx.Commit() } func sqlCommonDeleteUser(user User, dbHandle *sql.DB) error { @@ -232,8 +279,8 @@ func sqlCommonDeleteUser(user User, dbHandle *sql.DB) error { return err } -func sqlCommonDumpUsers(dbHandle *sql.DB) ([]User, error) { - users := []User{} +func sqlCommonDumpUsers(dbHandle sqlQuerier) ([]User, error) { + users := make([]User, 0, 100) q := getDumpUsersQuery() stmt, err := dbHandle.Prepare(q) if err != nil { @@ -242,26 +289,27 @@ func sqlCommonDumpUsers(dbHandle *sql.DB) ([]User, error) { } defer stmt.Close() rows, err := stmt.Query() - if err == nil { - defer rows.Close() - for rows.Next() { - u, err := getUserFromDbRow(nil, rows) - if err != nil { - return users, err - } - err = addCredentialsToUser(&u) - if err != nil { - return users, err - } - users = append(users, u) - } + if err != nil { + return users, err } - return users, err + defer rows.Close() + for rows.Next() { + u, err := getUserFromDbRow(nil, rows) + if err != nil { + return users, err + } + err = addCredentialsToUser(&u) + if err != nil { + return users, err + } + users = append(users, u) + } + return getUsersWithVirtualFolders(users, dbHandle) } -func sqlCommonGetUsers(limit int, offset int, order string, username string, dbHandle *sql.DB) ([]User, error) { - users := []User{} +func sqlCommonGetUsers(limit int, offset int, order string, username string, dbHandle sqlQuerier) ([]User, error) { + users := make([]User, 0, limit) q := getUsersQuery(order, username) stmt, err := dbHandle.Prepare(q) if err != nil { @@ -271,23 +319,25 @@ func sqlCommonGetUsers(limit int, offset int, order string, username string, dbH defer stmt.Close() var rows *sql.Rows if len(username) > 0 { - rows, err = stmt.Query(username, limit, offset) //nolint:rowserrcheck // err is checked + rows, err = stmt.Query(username, limit, offset) //nolint:rowserrcheck // rows.Err() is checked } else { - rows, err = stmt.Query(limit, offset) //nolint:rowserrcheck // err is checked + rows, err = stmt.Query(limit, offset) //nolint:rowserrcheck // rows.Err() is checked } if err == nil { defer rows.Close() for rows.Next() { u, err := getUserFromDbRow(nil, rows) - if err == nil { - users = append(users, HideUserSensitiveData(&u)) - } else { - break + if err != nil { + return users, err } + users = append(users, HideUserSensitiveData(&u)) } } - - return users, err + err = rows.Err() + if err != nil { + return users, err + } + return getUsersWithVirtualFolders(users, dbHandle) } func updateUserPermissionsFromDb(user *User, permissions string) error { @@ -316,18 +366,15 @@ func getUserFromDbRow(row *sql.Row, rows *sql.Rows) (User, error) { var publicKey sql.NullString var filters sql.NullString var fsConfig sql.NullString - var virtualFolders sql.NullString var err error if row != nil { err = row.Scan(&user.ID, &user.Username, &password, &publicKey, &user.HomeDir, &user.UID, &user.GID, &user.MaxSessions, &user.QuotaSize, &user.QuotaFiles, &permissions, &user.UsedQuotaSize, &user.UsedQuotaFiles, &user.LastQuotaUpdate, - &user.UploadBandwidth, &user.DownloadBandwidth, &user.ExpirationDate, &user.LastLogin, &user.Status, &filters, &fsConfig, - &virtualFolders) + &user.UploadBandwidth, &user.DownloadBandwidth, &user.ExpirationDate, &user.LastLogin, &user.Status, &filters, &fsConfig) } else { err = rows.Scan(&user.ID, &user.Username, &password, &publicKey, &user.HomeDir, &user.UID, &user.GID, &user.MaxSessions, &user.QuotaSize, &user.QuotaFiles, &permissions, &user.UsedQuotaSize, &user.UsedQuotaFiles, &user.LastQuotaUpdate, - &user.UploadBandwidth, &user.DownloadBandwidth, &user.ExpirationDate, &user.LastLogin, &user.Status, &filters, &fsConfig, - &virtualFolders) + &user.UploadBandwidth, &user.DownloadBandwidth, &user.ExpirationDate, &user.LastLogin, &user.Status, &filters, &fsConfig) } if err != nil { if err == sql.ErrNoRows { @@ -368,14 +415,308 @@ func getUserFromDbRow(row *sql.Row, rows *sql.Rows) (User, error) { user.FsConfig = fs } } - if virtualFolders.Valid { - var list []vfs.VirtualFolder - err = json.Unmarshal([]byte(virtualFolders.String), &list) - if err == nil { - user.VirtualFolders = list + return user, err +} + +func sqlCommonCheckFolderExists(name string, dbHandle sqlQuerier) (vfs.BaseVirtualFolder, error) { + var folder vfs.BaseVirtualFolder + q := getFolderByPathQuery() + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return folder, err + } + defer stmt.Close() + row := stmt.QueryRow(name) + err = row.Scan(&folder.ID, &folder.MappedPath, &folder.UsedQuotaSize, &folder.UsedQuotaFiles, &folder.LastQuotaUpdate) + if err == sql.ErrNoRows { + return folder, &RecordNotFoundError{err: err.Error()} + } + return folder, err +} + +func sqlCommonAddOrGetFolder(name string, usedQuotaSize int64, usedQuotaFiles int, lastQuotaUpdate int64, dbHandle sqlQuerier) (vfs.BaseVirtualFolder, error) { + folder, err := sqlCommonCheckFolderExists(name, dbHandle) + if _, ok := err.(*RecordNotFoundError); ok { + f := vfs.BaseVirtualFolder{ + MappedPath: name, + UsedQuotaSize: usedQuotaSize, + UsedQuotaFiles: usedQuotaFiles, + LastQuotaUpdate: lastQuotaUpdate, + } + err = sqlCommonAddFolder(f, dbHandle) + if err != nil { + return folder, err + } + return sqlCommonCheckFolderExists(name, dbHandle) + } + return folder, err +} + +func sqlCommonAddFolder(folder vfs.BaseVirtualFolder, dbHandle sqlQuerier) error { + err := validateFolder(&folder) + if err != nil { + return err + } + q := getAddFolderQuery() + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return err + } + defer stmt.Close() + _, err = stmt.Exec(folder.MappedPath, folder.UsedQuotaSize, folder.UsedQuotaFiles, folder.LastQuotaUpdate) + return err +} + +func sqlCommonDeleteFolder(folder vfs.BaseVirtualFolder, dbHandle sqlQuerier) error { + q := getDeleteFolderQuery() + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return err + } + defer stmt.Close() + _, err = stmt.Exec(folder.ID) + return err +} + +func sqlCommonDumpFolders(dbHandle sqlQuerier) ([]vfs.BaseVirtualFolder, error) { + folders := make([]vfs.BaseVirtualFolder, 0, 50) + q := getDumpFoldersQuery() + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return nil, err + } + defer stmt.Close() + rows, err := stmt.Query() + if err != nil { + return folders, err + } + defer rows.Close() + for rows.Next() { + var folder vfs.BaseVirtualFolder + err = rows.Scan(&folder.ID, &folder.MappedPath, &folder.UsedQuotaSize, &folder.UsedQuotaFiles, &folder.LastQuotaUpdate) + if err != nil { + return folders, err + } + folders = append(folders, folder) + } + err = rows.Err() + if err != nil { + return folders, err + } + return getVirtualFoldersWithUsers(folders, dbHandle) +} + +func sqlCommonGetFolders(limit, offset int, order, folderPath string, dbHandle sqlQuerier) ([]vfs.BaseVirtualFolder, error) { + folders := make([]vfs.BaseVirtualFolder, 0, limit) + q := getFoldersQuery(order, folderPath) + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return nil, err + } + defer stmt.Close() + var rows *sql.Rows + if len(folderPath) > 0 { + rows, err = stmt.Query(folderPath, limit, offset) //nolint:rowserrcheck // rows.Err() is checked + } else { + rows, err = stmt.Query(limit, offset) //nolint:rowserrcheck // rows.Err() is checked + } + if err != nil { + return folders, err + } + defer rows.Close() + for rows.Next() { + var folder vfs.BaseVirtualFolder + err = rows.Scan(&folder.ID, &folder.MappedPath, &folder.UsedQuotaSize, &folder.UsedQuotaFiles, &folder.LastQuotaUpdate) + if err != nil { + return folders, err + } + folders = append(folders, folder) + } + + err = rows.Err() + if err != nil { + return folders, err + } + return getVirtualFoldersWithUsers(folders, dbHandle) +} + +func sqlCommonClearFolderMapping(user User, dbHandle sqlQuerier) error { + q := getClearFolderMappingQuery() + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return err + } + defer stmt.Close() + _, err = stmt.Exec(user.Username) + return err +} + +func sqlCommonAddFolderMapping(user User, folder vfs.VirtualFolder, dbHandle sqlQuerier) error { + q := getAddFolderMappingQuery() + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return err + } + defer stmt.Close() + _, err = stmt.Exec(folder.VirtualPath, folder.QuotaSize, folder.QuotaFiles, folder.ID, user.Username) + return err +} + +func generateVirtualFoldersMapping(user User, dbHandle sqlQuerier) error { + err := sqlCommonClearFolderMapping(user, dbHandle) + if err != nil { + return err + } + for _, vfolder := range user.VirtualFolders { + f, err := sqlCommonAddOrGetFolder(vfolder.MappedPath, 0, 0, 0, dbHandle) + if err != nil { + return err + } + vfolder.BaseVirtualFolder = f + err = sqlCommonAddFolderMapping(user, vfolder, dbHandle) + if err != nil { + return err } } - return user, err + return err +} + +func getUserWithVirtualFolders(user User, dbHandle sqlQuerier) (User, error) { + users, err := getUsersWithVirtualFolders([]User{user}, dbHandle) + if err != nil { + return user, err + } + if len(users) == 0 { + return user, errSQLFoldersAssosaction + } + return users[0], err +} + +func getUsersWithVirtualFolders(users []User, dbHandle sqlQuerier) ([]User, error) { + var err error + usersVirtualFolders := make(map[int64][]vfs.VirtualFolder) + if len(users) == 0 { + return users, err + } + q := getRelatedFoldersForUsersQuery(users) + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return nil, err + } + defer stmt.Close() + rows, err := stmt.Query() + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + var folder vfs.VirtualFolder + var userID int64 + err = rows.Scan(&folder.ID, &folder.MappedPath, &folder.UsedQuotaSize, &folder.UsedQuotaFiles, + &folder.LastQuotaUpdate, &folder.VirtualPath, &folder.QuotaSize, &folder.QuotaFiles, &userID) + if err != nil { + return users, err + } + usersVirtualFolders[userID] = append(usersVirtualFolders[userID], folder) + } + err = rows.Err() + if err != nil { + return users, err + } + if len(usersVirtualFolders) == 0 { + return users, err + } + for idx := range users { + ref := &users[idx] + ref.VirtualFolders = usersVirtualFolders[ref.ID] + } + return users, err +} + +func getVirtualFoldersWithUsers(folders []vfs.BaseVirtualFolder, dbHandle sqlQuerier) ([]vfs.BaseVirtualFolder, error) { + var err error + vFoldersUsers := make(map[int64][]string) + if len(folders) == 0 { + return folders, err + } + q := getRelatedUsersForFoldersQuery(folders) + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return nil, err + } + defer stmt.Close() + rows, err := stmt.Query() + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + var username string + var folderID int64 + err = rows.Scan(&folderID, &username) + if err != nil { + return folders, err + } + vFoldersUsers[folderID] = append(vFoldersUsers[folderID], username) + } + err = rows.Err() + if err != nil { + return folders, err + } + if len(vFoldersUsers) == 0 { + return folders, err + } + for idx := range folders { + ref := &folders[idx] + ref.Users = vFoldersUsers[ref.ID] + } + return folders, err +} + +func sqlCommonUpdateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool, dbHandle *sql.DB) error { + q := getUpdateFolderQuotaQuery(reset) + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return err + } + defer stmt.Close() + _, err = stmt.Exec(sizeAdd, filesAdd, utils.GetTimeAsMsSinceEpoch(time.Now()), mappedPath) + if err == nil { + providerLog(logger.LevelDebug, "quota updated for folder %#v, files increment: %v size increment: %v is reset? %v", + mappedPath, filesAdd, sizeAdd, reset) + } else { + providerLog(logger.LevelWarn, "error updating quota for folder %#v: %v", mappedPath, err) + } + return err +} + +func sqlCommonGetFolderUsedQuota(mappedPath string, dbHandle *sql.DB) (int, int64, error) { + q := getQuotaFolderQuery() + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return 0, 0, err + } + defer stmt.Close() + + var usedFiles int + var usedSize int64 + err = stmt.QueryRow(mappedPath).Scan(&usedSize, &usedFiles) + if err != nil { + providerLog(logger.LevelWarn, "error getting quota for folder: %v, error: %v", mappedPath, err) + return 0, 0, err + } + return usedFiles, usedSize, err } func sqlCommonRollbackTransaction(tx *sql.Tx) { @@ -399,7 +740,7 @@ func sqlCommonGetDatabaseVersion(dbHandle *sql.DB) (schemaVersion, error) { return result, err } -func sqlCommonUpdateDatabaseVersion(dbHandle *sql.DB, version int) error { +func sqlCommonUpdateDatabaseVersion(dbHandle sqlQuerier, version int) error { q := getUpdateDBVersionQuery() stmt, err := dbHandle.Prepare(q) if err != nil { @@ -411,14 +752,139 @@ func sqlCommonUpdateDatabaseVersion(dbHandle *sql.DB, version int) error { return err } -func sqlCommonUpdateDatabaseVersionWithTX(tx *sql.Tx, version int) error { - q := getUpdateDBVersionQuery() - stmt, err := tx.Prepare(q) +func sqlCommonExecSQLAndUpdateDBVersion(dbHandle *sql.DB, sql []string, newVersion int) error { + tx, err := dbHandle.Begin() if err != nil { - providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) return err } + for _, q := range sql { + if len(strings.TrimSpace(q)) == 0 { + continue + } + _, err = tx.Exec(q) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + } + err = sqlCommonUpdateDatabaseVersion(tx, newVersion) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + return tx.Commit() +} + +func sqlCommonGetCompatVirtualFolders(dbHandle *sql.DB) ([]userCompactVFolders, error) { + users := []userCompactVFolders{} + q := getCompatVirtualFoldersQuery() + stmt, err := dbHandle.Prepare(q) + if err != nil { + providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err) + return nil, err + } defer stmt.Close() - _, err = stmt.Exec(version) + rows, err := stmt.Query() + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + var user userCompactVFolders + var virtualFolders sql.NullString + err = rows.Scan(&user.ID, &user.Username, &virtualFolders) + if err != nil { + return nil, err + } + if virtualFolders.Valid { + var list []virtualFoldersCompact + err = json.Unmarshal([]byte(virtualFolders.String), &list) + if err == nil && len(list) > 0 { + user.VirtualFolders = list + users = append(users, user) + } + } + } + return users, rows.Err() +} + +func sqlCommonRestoreCompatVirtualFolders(users []userCompactVFolders, dbHandle sqlQuerier) ([]string, error) { + foldersToScan := []string{} + for _, user := range users { + for _, vfolder := range user.VirtualFolders { + providerLog(logger.LevelInfo, "restoring virtual folder: %+v for user %#v", vfolder, user.Username) + // -1 means included in user quota, 0 means unlimited + quotaSize := int64(-1) + quotaFiles := -1 + if vfolder.ExcludeFromQuota { + quotaFiles = 0 + quotaSize = 0 + } + b, err := sqlCommonAddOrGetFolder(vfolder.MappedPath, 0, 0, 0, dbHandle) + if err != nil { + providerLog(logger.LevelWarn, "error restoring virtual folder for user %#v: %v", user.Username, err) + return foldersToScan, err + } + u := User{ + ID: user.ID, + Username: user.Username, + } + f := vfs.VirtualFolder{ + BaseVirtualFolder: b, + VirtualPath: vfolder.VirtualPath, + QuotaSize: quotaSize, + QuotaFiles: quotaFiles, + } + err = sqlCommonAddFolderMapping(u, f, dbHandle) + if err != nil { + providerLog(logger.LevelWarn, "error adding virtual folder mapping for user %#v: %v", user.Username, err) + return foldersToScan, err + } + if !utils.IsStringInSlice(vfolder.MappedPath, foldersToScan) { + foldersToScan = append(foldersToScan, vfolder.MappedPath) + } + providerLog(logger.LevelInfo, "virtual folder: %+v for user %#v successfully restored", vfolder, user.Username) + } + } + return foldersToScan, nil +} + +func sqlCommonUpdateDatabaseFrom3To4(sqlV4 string, dbHandle *sql.DB) error { + providerLog(logger.LevelInfo, "updating database version: 3 -> 4") + users, err := sqlCommonGetCompatVirtualFolders(dbHandle) + if err != nil { + return err + } + sql := strings.ReplaceAll(sqlV4, "{{users}}", sqlTableUsers) + sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders) + sql = strings.ReplaceAll(sql, "{{folders_mapping}}", sqlTableFoldersMapping) + tx, err := dbHandle.Begin() + if err != nil { + return err + } + for _, q := range strings.Split(sql, ";") { + if len(strings.TrimSpace(q)) == 0 { + continue + } + _, err = tx.Exec(q) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + } + foldersToScan, err := sqlCommonRestoreCompatVirtualFolders(users, tx) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + err = sqlCommonUpdateDatabaseVersion(tx, 4) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + err = tx.Commit() + if err == nil { + go updateVFoldersQuotaAfterRestore(foldersToScan) + } return err } diff --git a/dataprovider/sqlite.go b/dataprovider/sqlite.go index 2556e08d..a8d721a0 100644 --- a/dataprovider/sqlite.go +++ b/dataprovider/sqlite.go @@ -13,6 +13,7 @@ import ( "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/utils" + "github.com/drakkan/sftpgo/vfs" ) const ( @@ -23,9 +24,9 @@ NOT NULL UNIQUE, "password" varchar(255) NULL, "public_keys" text NULL, "home_di "last_quota_update" bigint NOT NULL, "upload_bandwidth" integer NOT NULL, "download_bandwidth" integer NOT NULL, "expiration_date" bigint NOT NULL, "last_login" bigint NOT NULL, "status" integer NOT NULL, "filters" text NULL, "filesystem" text NULL);` - sqliteSchemaTableSQL = `CREATE TABLE "schema_version" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "version" integer NOT NULL);` - sqliteUsersV2SQL = `ALTER TABLE "{{users}}" ADD COLUMN "virtual_folders" text NULL;` - sqliteUsersV3SQL = `CREATE TABLE "new__users" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "username" varchar(255) NOT NULL UNIQUE, + sqliteSchemaTableSQL = `CREATE TABLE "{{schema_version}}" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "version" integer NOT NULL);` + sqliteV2SQL = `ALTER TABLE "{{users}}" ADD COLUMN "virtual_folders" text NULL;` + sqliteV3SQL = `CREATE TABLE "new__users" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "username" varchar(255) NOT NULL UNIQUE, "password" text NULL, "public_keys" text NULL, "home_dir" varchar(255) NOT NULL, "uid" integer NOT NULL, "gid" integer NOT NULL, "max_sessions" integer NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "permissions" text NOT NULL, "used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL, @@ -39,6 +40,27 @@ INSERT INTO "new__users" ("id", "username", "public_keys", "home_dir", "uid", "g "password" FROM "{{users}}"; DROP TABLE "{{users}}"; ALTER TABLE "new__users" RENAME TO "{{users}}";` + sqliteV4SQL = `CREATE TABLE "{{folders}}" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "path" varchar(512) NOT NULL UNIQUE, +"used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL); +CREATE TABLE "{{folders_mapping}}" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "virtual_path" varchar(512) NOT NULL, +"quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "folder_id" integer NOT NULL REFERENCES "{{folders}}" ("id") +ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, "user_id" integer NOT NULL REFERENCES "{{users}}" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, +CONSTRAINT "unique_mapping" UNIQUE ("user_id", "folder_id")); +CREATE TABLE "new__users" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "username" varchar(255) NOT NULL UNIQUE, "password" text NULL, +"public_keys" text NULL, "home_dir" varchar(512) NOT NULL, "uid" integer NOT NULL, "gid" integer NOT NULL, "max_sessions" integer NOT NULL, +"quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "permissions" text NOT NULL, "used_quota_size" bigint NOT NULL, +"used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL, "upload_bandwidth" integer NOT NULL, "download_bandwidth" integer NOT NULL, +"expiration_date" bigint NOT NULL, "last_login" bigint NOT NULL, "status" integer NOT NULL, "filters" text NULL, "filesystem" text NULL); +INSERT INTO "new__users" ("id", "username", "password", "public_keys", "home_dir", "uid", "gid", "max_sessions", "quota_size", "quota_files", +"permissions", "used_quota_size", "used_quota_files", "last_quota_update", "upload_bandwidth", "download_bandwidth", "expiration_date", +"last_login", "status", "filters", "filesystem") SELECT "id", "username", "password", "public_keys", "home_dir", "uid", "gid", "max_sessions", +"quota_size", "quota_files", "permissions", "used_quota_size", "used_quota_files", "last_quota_update", "upload_bandwidth", "download_bandwidth", +"expiration_date", "last_login", "status", "filters", "filesystem" FROM "{{users}}"; +DROP TABLE "{{users}}"; +ALTER TABLE "new__users" RENAME TO "{{users}}"; +CREATE INDEX "folders_mapping_folder_id_idx" ON "{{folders_mapping}}" ("folder_id"); +CREATE INDEX "folders_mapping_user_id_idx" ON "{{folders_mapping}}" ("user_id"); +` ) // SQLiteProvider auth provider for SQLite database @@ -62,7 +84,7 @@ func initializeSQLiteProvider(basePath string) error { if !filepath.IsAbs(dbPath) { dbPath = filepath.Join(basePath, dbPath) } - connectionString = fmt.Sprintf("file:%v?cache=shared", dbPath) + connectionString = fmt.Sprintf("file:%v?cache=shared&_foreign_keys=1", dbPath) } else { connectionString = config.ConnectionString } @@ -98,14 +120,14 @@ func (p SQLiteProvider) updateQuota(username string, filesAdd int, sizeAdd int64 return sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle) } -func (p SQLiteProvider) updateLastLogin(username string) error { - return sqlCommonUpdateLastLogin(username, p.dbHandle) -} - func (p SQLiteProvider) getUsedQuota(username string) (int, int64, error) { return sqlCommonGetUsedQuota(username, p.dbHandle) } +func (p SQLiteProvider) updateLastLogin(username string) error { + return sqlCommonUpdateLastLogin(username, p.dbHandle) +} + func (p SQLiteProvider) userExists(username string) (User, error) { return sqlCommonCheckUserExists(username, p.dbHandle) } @@ -130,6 +152,34 @@ func (p SQLiteProvider) getUsers(limit int, offset int, order string, username s return sqlCommonGetUsers(limit, offset, order, username, p.dbHandle) } +func (p SQLiteProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) { + return sqlCommonDumpFolders(p.dbHandle) +} + +func (p SQLiteProvider) getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error) { + return sqlCommonGetFolders(limit, offset, order, folderPath, p.dbHandle) +} + +func (p SQLiteProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error) { + return sqlCommonCheckFolderExists(mappedPath, p.dbHandle) +} + +func (p SQLiteProvider) addFolder(folder vfs.BaseVirtualFolder) error { + return sqlCommonAddFolder(folder, p.dbHandle) +} + +func (p SQLiteProvider) deleteFolder(folder vfs.BaseVirtualFolder) error { + return sqlCommonDeleteFolder(folder, p.dbHandle) +} + +func (p SQLiteProvider) updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error { + return sqlCommonUpdateFolderQuota(mappedPath, filesAdd, sizeAdd, reset, p.dbHandle) +} + +func (p SQLiteProvider) getUsedFolderQuota(mappedPath string) (int, int64, error) { + return sqlCommonGetFolderUsedQuota(mappedPath, p.dbHandle) +} + func (p SQLiteProvider) close() error { return p.dbHandle.Close() } @@ -140,10 +190,27 @@ func (p SQLiteProvider) reloadConfig() error { // initializeDatabase creates the initial database structure func (p SQLiteProvider) initializeDatabase() error { - sqlUsers := strings.Replace(sqliteUsersTableSQL, "{{users}}", config.UsersTable, 1) - sql := sqlUsers + " " + sqliteSchemaTableSQL + " " + initialDBVersionSQL - _, err := p.dbHandle.Exec(sql) - return err + sqlUsers := strings.Replace(sqliteUsersTableSQL, "{{users}}", sqlTableUsers, 1) + tx, err := p.dbHandle.Begin() + if err != nil { + return err + } + _, err = tx.Exec(sqlUsers) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + _, err = tx.Exec(strings.Replace(sqliteSchemaTableSQL, "{{schema_version}}", sqlTableSchemaVersion, 1)) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + _, err = tx.Exec(strings.Replace(initialDBVersionSQL, "{{schema_version}}", sqlTableSchemaVersion, 1)) + if err != nil { + sqlCommonRollbackTransaction(tx) + return err + } + return tx.Commit() } func (p SQLiteProvider) migrateDatabase() error { @@ -161,9 +228,19 @@ func (p SQLiteProvider) migrateDatabase() error { if err != nil { return err } - return updateSQLiteDatabaseFrom2To3(p.dbHandle) + err = updateSQLiteDatabaseFrom2To3(p.dbHandle) + if err != nil { + return err + } + return updateSQLiteDatabaseFrom3To4(p.dbHandle) case 2: - return updateSQLiteDatabaseFrom2To3(p.dbHandle) + err = updateSQLiteDatabaseFrom2To3(p.dbHandle) + if err != nil { + return err + } + return updateSQLiteDatabaseFrom3To4(p.dbHandle) + case 3: + return updateSQLiteDatabaseFrom3To4(p.dbHandle) default: return fmt.Errorf("Database version not handled: %v", dbVersion.Version) } @@ -171,20 +248,16 @@ func (p SQLiteProvider) migrateDatabase() error { func updateSQLiteDatabaseFrom1To2(dbHandle *sql.DB) error { providerLog(logger.LevelInfo, "updating database version: 1 -> 2") - sql := strings.Replace(sqliteUsersV2SQL, "{{users}}", config.UsersTable, 1) - _, err := dbHandle.Exec(sql) - if err != nil { - return err - } - return sqlCommonUpdateDatabaseVersion(dbHandle, 2) + sql := strings.Replace(sqliteV2SQL, "{{users}}", sqlTableUsers, 1) + return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2) } func updateSQLiteDatabaseFrom2To3(dbHandle *sql.DB) error { providerLog(logger.LevelInfo, "updating database version: 2 -> 3") - sql := strings.ReplaceAll(sqliteUsersV3SQL, "{{users}}", config.UsersTable) - _, err := dbHandle.Exec(sql) - if err != nil { - return err - } - return sqlCommonUpdateDatabaseVersion(dbHandle, 3) + sql := strings.ReplaceAll(sqliteV3SQL, "{{users}}", sqlTableUsers) + return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3) +} + +func updateSQLiteDatabaseFrom3To4(dbHandle *sql.DB) error { + return sqlCommonUpdateDatabaseFrom3To4(sqliteV4SQL, dbHandle) } diff --git a/dataprovider/sqlqueries.go b/dataprovider/sqlqueries.go index 3580af14..193b3044 100644 --- a/dataprovider/sqlqueries.go +++ b/dataprovider/sqlqueries.go @@ -1,11 +1,17 @@ package dataprovider -import "fmt" +import ( + "fmt" + "strconv" + "strings" + + "github.com/drakkan/sftpgo/vfs" +) const ( selectUserFields = "id,username,password,public_keys,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions,used_quota_size," + - "used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth,expiration_date,last_login,status,filters,filesystem," + - "virtual_folders" + "used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth,expiration_date,last_login,status,filters,filesystem" + selectFolderFields = "id,path,used_quota_size,used_quota_files,last_quota_update" ) func getSQLPlaceholders() []string { @@ -21,71 +27,160 @@ func getSQLPlaceholders() []string { } func getUserByUsernameQuery() string { - return fmt.Sprintf(`SELECT %v FROM %v WHERE username = %v`, selectUserFields, config.UsersTable, sqlPlaceholders[0]) + return fmt.Sprintf(`SELECT %v FROM %v WHERE username = %v`, selectUserFields, sqlTableUsers, sqlPlaceholders[0]) } func getUserByIDQuery() string { - return fmt.Sprintf(`SELECT %v FROM %v WHERE id = %v`, selectUserFields, config.UsersTable, sqlPlaceholders[0]) + return fmt.Sprintf(`SELECT %v FROM %v WHERE id = %v`, selectUserFields, sqlTableUsers, sqlPlaceholders[0]) } func getUsersQuery(order string, username string) string { if len(username) > 0 { return fmt.Sprintf(`SELECT %v FROM %v WHERE username = %v ORDER BY username %v LIMIT %v OFFSET %v`, - selectUserFields, config.UsersTable, sqlPlaceholders[0], order, sqlPlaceholders[1], sqlPlaceholders[2]) + selectUserFields, sqlTableUsers, sqlPlaceholders[0], order, sqlPlaceholders[1], sqlPlaceholders[2]) } - return fmt.Sprintf(`SELECT %v FROM %v ORDER BY username %v LIMIT %v OFFSET %v`, selectUserFields, config.UsersTable, + return fmt.Sprintf(`SELECT %v FROM %v ORDER BY username %v LIMIT %v OFFSET %v`, selectUserFields, sqlTableUsers, order, sqlPlaceholders[0], sqlPlaceholders[1]) } func getDumpUsersQuery() string { - return fmt.Sprintf(`SELECT %v FROM %v`, selectUserFields, config.UsersTable) + return fmt.Sprintf(`SELECT %v FROM %v`, selectUserFields, sqlTableUsers) +} + +func getDumpFoldersQuery() string { + return fmt.Sprintf(`SELECT %v FROM %v`, selectFolderFields, sqlTableFolders) } func getUpdateQuotaQuery(reset bool) string { if reset { return fmt.Sprintf(`UPDATE %v SET used_quota_size = %v,used_quota_files = %v,last_quota_update = %v - WHERE username = %v`, config.UsersTable, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3]) + WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3]) } return fmt.Sprintf(`UPDATE %v SET used_quota_size = used_quota_size + %v,used_quota_files = used_quota_files + %v,last_quota_update = %v - WHERE username = %v`, config.UsersTable, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3]) + WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3]) } func getUpdateLastLoginQuery() string { - return fmt.Sprintf(`UPDATE %v SET last_login = %v WHERE username = %v`, config.UsersTable, sqlPlaceholders[0], sqlPlaceholders[1]) + return fmt.Sprintf(`UPDATE %v SET last_login = %v WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1]) } func getQuotaQuery() string { - return fmt.Sprintf(`SELECT used_quota_size,used_quota_files FROM %v WHERE username = %v`, config.UsersTable, + return fmt.Sprintf(`SELECT used_quota_size,used_quota_files FROM %v WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0]) } func getAddUserQuery() string { return fmt.Sprintf(`INSERT INTO %v (username,password,public_keys,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions, used_quota_size,used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth,status,last_login,expiration_date,filters, - filesystem,virtual_folders) - VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0,0,0,%v,%v,%v,0,%v,%v,%v,%v)`, config.UsersTable, sqlPlaceholders[0], sqlPlaceholders[1], + filesystem) + VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0,0,0,%v,%v,%v,0,%v,%v,%v)`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9], sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13], - sqlPlaceholders[14], sqlPlaceholders[15], sqlPlaceholders[16]) + sqlPlaceholders[14], sqlPlaceholders[15]) } func getUpdateUserQuery() string { return fmt.Sprintf(`UPDATE %v SET password=%v,public_keys=%v,home_dir=%v,uid=%v,gid=%v,max_sessions=%v,quota_size=%v, - quota_files=%v,permissions=%v,upload_bandwidth=%v,download_bandwidth=%v,status=%v,expiration_date=%v,filters=%v,filesystem=%v, - virtual_folders=%v WHERE id = %v`, config.UsersTable, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], + quota_files=%v,permissions=%v,upload_bandwidth=%v,download_bandwidth=%v,status=%v,expiration_date=%v,filters=%v,filesystem=%v + WHERE id = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9], - sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13], sqlPlaceholders[14], sqlPlaceholders[15], - sqlPlaceholders[16]) + sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13], sqlPlaceholders[14], sqlPlaceholders[15]) } func getDeleteUserQuery() string { - return fmt.Sprintf(`DELETE FROM %v WHERE id = %v`, config.UsersTable, sqlPlaceholders[0]) + return fmt.Sprintf(`DELETE FROM %v WHERE id = %v`, sqlTableUsers, sqlPlaceholders[0]) +} + +func getFolderByPathQuery() string { + return fmt.Sprintf(`SELECT %v FROM %v WHERE path = %v`, selectFolderFields, sqlTableFolders, sqlPlaceholders[0]) +} + +func getAddFolderQuery() string { + return fmt.Sprintf(`INSERT INTO %v (path,used_quota_size,used_quota_files,last_quota_update) VALUES (%v,%v,%v,%v)`, + sqlTableFolders, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3]) +} + +func getDeleteFolderQuery() string { + return fmt.Sprintf(`DELETE FROM %v WHERE id = %v`, sqlTableFolders, sqlPlaceholders[0]) +} + +func getClearFolderMappingQuery() string { + return fmt.Sprintf(`DELETE FROM %v WHERE user_id = (SELECT id FROM %v WHERE username = %v)`, sqlTableFoldersMapping, + sqlTableUsers, sqlPlaceholders[0]) +} + +func getAddFolderMappingQuery() string { + return fmt.Sprintf(`INSERT INTO %v (virtual_path,quota_size,quota_files,folder_id,user_id) + VALUES (%v,%v,%v,%v,(SELECT id FROM %v WHERE username = %v))`, sqlTableFoldersMapping, sqlPlaceholders[0], + sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3], sqlTableUsers, sqlPlaceholders[4]) +} + +func getFoldersQuery(order, folderPath string) string { + if len(folderPath) > 0 { + return fmt.Sprintf(`SELECT %v FROM %v WHERE path = %v ORDER BY path %v LIMIT %v OFFSET %v`, + selectFolderFields, sqlTableFolders, sqlPlaceholders[0], order, sqlPlaceholders[1], sqlPlaceholders[2]) + } + return fmt.Sprintf(`SELECT %v FROM %v ORDER BY path %v LIMIT %v OFFSET %v`, selectFolderFields, sqlTableFolders, + order, sqlPlaceholders[0], sqlPlaceholders[1]) +} + +func getUpdateFolderQuotaQuery(reset bool) string { + if reset { + return fmt.Sprintf(`UPDATE %v SET used_quota_size = %v,used_quota_files = %v,last_quota_update = %v + WHERE path = %v`, sqlTableFolders, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3]) + } + return fmt.Sprintf(`UPDATE %v SET used_quota_size = used_quota_size + %v,used_quota_files = used_quota_files + %v,last_quota_update = %v + WHERE path = %v`, sqlTableFolders, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3]) +} + +func getQuotaFolderQuery() string { + return fmt.Sprintf(`SELECT used_quota_size,used_quota_files FROM %v WHERE path = %v`, sqlTableFolders, + sqlPlaceholders[0]) +} + +func getRelatedFoldersForUsersQuery(users []User) string { + var sb strings.Builder + for _, u := range users { + if sb.Len() == 0 { + sb.WriteString("(") + } else { + sb.WriteString(",") + } + sb.WriteString(strconv.FormatInt(u.ID, 10)) + } + if sb.Len() > 0 { + sb.WriteString(")") + } + return fmt.Sprintf(`SELECT f.id,f.path,f.used_quota_size,f.used_quota_files,f.last_quota_update,fm.virtual_path,fm.quota_size,fm.quota_files,fm.user_id + FROM %v f INNER JOIN %v fm ON f.id = fm.folder_id WHERE fm.user_id IN %v ORDER BY fm.user_id`, sqlTableFolders, + sqlTableFoldersMapping, sb.String()) +} + +func getRelatedUsersForFoldersQuery(folders []vfs.BaseVirtualFolder) string { + var sb strings.Builder + for _, f := range folders { + if sb.Len() == 0 { + sb.WriteString("(") + } else { + sb.WriteString(",") + } + sb.WriteString(strconv.FormatInt(f.ID, 10)) + } + if sb.Len() > 0 { + sb.WriteString(")") + } + return fmt.Sprintf(`SELECT fm.folder_id,u.username FROM %v fm INNER JOIN %v u ON fm.user_id = u.id + WHERE fm.folder_id IN %v ORDER BY fm.folder_id`, sqlTableFoldersMapping, sqlTableUsers, sb.String()) } func getDatabaseVersionQuery() string { - return "SELECT version from schema_version LIMIT 1" + return fmt.Sprintf("SELECT version from %v LIMIT 1", sqlTableSchemaVersion) } func getUpdateDBVersionQuery() string { - return fmt.Sprintf(`UPDATE schema_version SET version=%v`, sqlPlaceholders[0]) + return fmt.Sprintf(`UPDATE %v SET version=%v`, sqlTableSchemaVersion, sqlPlaceholders[0]) +} + +func getCompatVirtualFoldersQuery() string { + return fmt.Sprintf(`SELECT id,username,virtual_folders FROM %v`, sqlTableUsers) } diff --git a/dataprovider/user.go b/dataprovider/user.go index 6d90ea4d..3fe91de3 100644 --- a/dataprovider/user.go +++ b/dataprovider/user.go @@ -2,6 +2,7 @@ package dataprovider import ( "encoding/json" + "errors" "fmt" "net" "os" @@ -54,6 +55,10 @@ const ( SSHLoginMethodKeyAndKeyboardInt = "publickey+keyboard-interactive" ) +var ( + errNoMatchingVirtualFolder = errors.New("no matching virtual folder found") +) + // ExtensionsFilter defines filters based on file extensions. // These restrictions do not apply to files listing for performance reasons, so // a denied file cannot be downloaded/overwritten/renamed but will still be @@ -121,7 +126,8 @@ type User struct { PublicKeys []string `json:"public_keys,omitempty"` // The user cannot upload or download files outside this directory. Must be an absolute path HomeDir string `json:"home_dir"` - // Mapping between virtual paths and filesystem paths outside the home directory. Supported for local filesystem only + // Mapping between virtual paths and filesystem paths outside the home directory. + // Supported for local filesystem only VirtualFolders []vfs.VirtualFolder `json:"virtual_folders,omitempty"` // If sftpgo runs as root system user then the created files and directories will be assigned to this system UID UID int `json:"uid"` @@ -191,20 +197,22 @@ func (u *User) GetPermissionsForPath(p string) []string { return permissions } -// IsFileExcludedFromQuota returns true if the file must be excluded from quota usage -func (u *User) IsFileExcludedFromQuota(sftpPath string) bool { +// GetVirtualFolderForPath returns the virtual folder containing the specified sftp path. +// If the path is not inside a virtual folder an error is returned +func (u *User) GetVirtualFolderForPath(sftpPath string) (vfs.VirtualFolder, error) { + var folder vfs.VirtualFolder if len(u.VirtualFolders) == 0 || u.FsConfig.Provider != 0 { - return false + return folder, errNoMatchingVirtualFolder } dirsForPath := utils.GetDirsForSFTPPath(path.Dir(sftpPath)) for _, val := range dirsForPath { for _, v := range u.VirtualFolders { if v.VirtualPath == val { - return v.ExcludeFromQuota + return v, nil } } } - return false + return folder, errNoMatchingVirtualFolder } // AddVirtualDirs adds virtual folders, if defined, to the given files list @@ -241,6 +249,19 @@ func (u *User) IsVirtualFolder(sftpPath string) bool { return false } +// HasVirtualFoldersInside return true if there are virtual folders inside the +// specified SFTP path. We assume that path are cleaned +func (u *User) HasVirtualFoldersInside(sftpPath string) bool { + for _, v := range u.VirtualFolders { + if len(v.VirtualPath) > len(sftpPath) { + if strings.HasPrefix(v.VirtualPath, sftpPath+"/") { + return true + } + } + } + return false +} + // HasPerm returns true if the user has the given permission or any permission func (u *User) HasPerm(permission, path string) bool { perms := u.GetPermissionsForPath(path) @@ -264,6 +285,14 @@ func (u *User) HasPerms(permissions []string, path string) bool { return true } +// HasNoQuotaRestrictions returns true if no quota restrictions need to be applyed +func (u *User) HasNoQuotaRestrictions(checkFiles bool) bool { + if u.QuotaSize == 0 && (!checkFiles || u.QuotaFiles == 0) { + return true + } + return false +} + // IsLoginMethodAllowed returns true if the specified login method is allowed func (u *User) IsLoginMethodAllowed(loginMethod string, partialSuccessMethods []string) bool { if len(u.Filters.DeniedLoginMethods) == 0 { @@ -421,11 +450,6 @@ func (u *User) GetFsConfigAsJSON() ([]byte, error) { return json.Marshal(u.FsConfig) } -// GetVirtualFoldersAsJSON returns the virtual folders as json byte array -func (u *User) GetVirtualFoldersAsJSON() ([]byte, error) { - return json.Marshal(u.VirtualFolders) -} - // GetUID returns a validate uid, suitable for use with os.Chown func (u *User) GetUID() int { if u.UID <= 0 || u.UID > 65535 { diff --git a/docker/sftpgo/alpine/README.md b/docker/sftpgo/alpine/README.md index 859c2035..c3f66be8 100644 --- a/docker/sftpgo/alpine/README.md +++ b/docker/sftpgo/alpine/README.md @@ -15,7 +15,7 @@ sudo groupadd -g 1003 sftpgrp && \ # Get and build SFTPGo image. # Add --build-arg TAG=LATEST to build the latest tag or e.g. TAG=0.9.6 for a specific tag/commit. -# Add --build-arg FEATURES= to specify the feature to build. +# Add --build-arg FEATURES= to specify the features to build. git clone https://github.com/drakkan/sftpgo.git && \ cd sftpgo && \ sudo docker build -t sftpgo docker/sftpgo/alpine/ diff --git a/docs/account.md b/docs/account.md index a886e77a..29f6c18f 100644 --- a/docs/account.md +++ b/docs/account.md @@ -8,7 +8,7 @@ For each account, the following properties can be configured: - `status` 1 means "active", 0 "inactive". An inactive account cannot login. - `expiration_date` expiration date as unix timestamp in milliseconds. An expired account cannot login. 0 means no expiration. - `home_dir` the user cannot upload or download files outside this directory. Must be an absolute path. A local home directory is required for Cloud Storage Backends too: in this case it will store temporary files. -- `virtual_folders` list of mappings between virtual SFTP/SCP paths and local filesystem paths outside the user home directory. The specified paths must be absolute and the virtual path cannot be "/", it must be a sub directory. The parent directory for the specified virtual path must exist. SFTPGo will try to automatically create any missing parent directory for the configured virtual folders at user login. For each mapping you can configure if the folder will be included or not in user quota limit. +- `virtual_folders` list of mappings between virtual SFTP/SCP paths and local filesystem paths outside the user home directory. More information can be found [here](./virtual-folders.md) - `uid`, `gid`. If SFTPGo runs as root system user then the created files and directories will be assigned to this system uid/gid. Ignored on windows or if SFTPGo runs as non root user: in this case files and directories for all SFTP users will be owned by the system user that runs SFTPGo. - `max_sessions` maximum concurrent sessions. 0 means unlimited. - `quota_size` maximum size allowed as bytes. 0 means unlimited. @@ -47,14 +47,14 @@ For each account, the following properties can be configured: - `s3_access_secret`, if provided it is stored encrypted (AES-256-GCM). You can leave access key and access secret blank to use credentials from environment - `s3_endpoint`, specifies a S3 endpoint (server) different from AWS. It is not required if you are connecting to AWS - `s3_storage_class`, leave blank to use the default or specify a valid AWS [storage class](https://docs.aws.amazon.com/AmazonS3/latest/dev/storage-class-intro.html) -- `s3_key_prefix`, allows to restrict access to the virtual folder identified by this prefix and its contents +- `s3_key_prefix`, allows to restrict access to the folder identified by this prefix and its contents - `s3_upload_part_size`, the buffer size for multipart uploads (MB). Zero means the default (5 MB). Minimum is 5 - `s3_upload_concurrency` how many parts are uploaded in parallel - `gcs_bucket`, required for GCS filesystem - `gcs_credentials`, Google Cloud Storage JSON credentials base64 encoded - `gcs_automatic_credentials`, integer. Set to 1 to use Application Default Credentials strategy or set to 0 to use explicit credentials via `gcs_credentials` - `gcs_storage_class` -- `gcs_key_prefix`, allows to restrict access to the virtual folder identified by this prefix and its contents +- `gcs_key_prefix`, allows to restrict access to the folder identified by this prefix and its contents These properties are stored inside the data provider. diff --git a/docs/full-configuration.md b/docs/full-configuration.md index 0f65f173..341aef1a 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -84,12 +84,12 @@ The configuration file contains the following sections: - `password`, string. Database password. Leave empty for drivers `sqlite`, `bolt` and `memory` - `sslmode`, integer. Used for drivers `mysql` and `postgresql`. 0 disable SSL/TLS connections, 1 require ssl, 2 set ssl mode to `verify-ca` for driver `postgresql` and `skip-verify` for driver `mysql`, 3 set ssl mode to `verify-full` for driver `postgresql` and `preferred` for driver `mysql` - `connectionstring`, string. Provide a custom database connection string. If not empty, this connection string will be used instead of building one using the previous parameters. Leave empty for drivers `bolt` and `memory` - - `users_table`, string. Database table for SFTP users + - `sql_tables_prefix`, string. Prefix for SQL tables - `manage_users`, integer. Set to 0 to disable users management, 1 to enable - `track_quota`, integer. Set the preferred mode to track users quota between the following choices: - - 0, disable quota tracking. REST API to scan user dir and update quota will do nothing + - 0, disable quota tracking. REST API to scan users home directories/virtual folders and update quota will do nothing - 1, quota is updated each time a user uploads or deletes a file, even if the user has no quota restrictions - - 2, quota is updated each time a user uploads or deletes a file, but only for users with quota restrictions. With this configuration, the "quota scan" REST API can still be used to periodically update space usage for users without quota restrictions + - 2, quota is updated each time a user uploads or deletes a file, but only for users with quota restrictions and for virtual folders. With this configuration, the `quota scan` and `folder_quota_scan` REST API can still be used to periodically update space usage for users without quota restrictions and for folders - `pool_size`, integer. Sets the maximum number of open connections for `mysql` and `postgresql` driver. Default 0 (unlimited) - `users_base_dir`, string. Users default base directory. If no home dir is defined while adding a new user, and this value is a valid absolute path, then the user home dir will be automatically defined as the path obtained joining the base dir and the username - `actions`, struct. It contains the command to execute and/or the HTTP URL to notify and the trigger conditions. See the "Custom Actions" paragraph for more details diff --git a/docs/google-cloud-storage.md b/docs/google-cloud-storage.md index 972d40c9..92e59e0d 100644 --- a/docs/google-cloud-storage.md +++ b/docs/google-cloud-storage.md @@ -2,7 +2,7 @@ To connect SFTPGo to Google Cloud Storage you can use use the Application Default Credentials (ADC) strategy to try to find your application's credentials automatically or you can explicitly provide a JSON credentials file that you can obtain from the Google Cloud Console. Take a look [here](https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application) for details. -Specifying a different `key_prefix`, you can assign different virtual folders of the same bucket to different users. This is similar to a chroot directory for local filesystem. Each SFTP/SCP user can only access the assigned virtual folder and its contents. The virtual folder identified by `key_prefix` does not need to be pre-created. +Specifying a different `key_prefix`, you can assign different "folders" of the same bucket to different users. This is similar to a chroot directory for local filesystem. Each SFTP/SCP user can only access the assigned folder and its contents. The folder identified by `key_prefix` does not need to be pre-created. You can optionally specify a [storage class](https://cloud.google.com/storage/docs/storage-classes) too. Leave it blank to use the default storage class. diff --git a/docs/s3.md b/docs/s3.md index 23015d46..ef26ad67 100644 --- a/docs/s3.md +++ b/docs/s3.md @@ -11,7 +11,7 @@ So, you need to provide access keys to activate option 1, or leave them blank to Most S3 backends require HTTPS connections so if you are running SFTPGo as docker image please be sure to uncomment the line that install `ca-certificates`, inside your `Dockerfile`, to be able to properly verify certificate authorities. -Specifying a different `key_prefix`, you can assign different virtual folders of the same bucket to different users. This is similar to a chroot directory for local filesystem. Each SFTP/SCP user can only access the assigned virtual folder and its contents. The virtual folder identified by `key_prefix` does not need to be pre-created. +Specifying a different `key_prefix`, you can assign different "folders" of the same bucket to different users. This is similar to a chroot directory for local filesystem. Each SFTP/SCP user can only access the assigned folder and its contents. The folder identified by `key_prefix` does not need to be pre-created. SFTPGo uses multipart uploads and parallel downloads for storing and retrieving files from S3. diff --git a/examples/rest-api-cli/README.md b/examples/rest-api-cli/README.md index bc1e9f48..cbe5a6dc 100644 --- a/examples/rest-api-cli/README.md +++ b/examples/rest-api-cli/README.md @@ -140,7 +140,7 @@ Output: Command: ``` -python sftpgo_api_cli.py update-user 9576 test_username --password "test_pwd" --home-dir="/tmp/test_home_dir" --uid 0 --gid 33 --max-sessions 3 --quota-size 0 --quota-files 4 --permissions "*" --subdirs-permissions "/dir1::list,download,create_symlinks" --upload-bandwidth 90 --download-bandwidth 80 --status 1 --expiration-date "" --allowed-ip "" --denied-ip "192.168.1.0/24" --denied-login-methods "" --fs local --virtual-folders "/vdir1::/tmp/mapped1" "/vdir2::/tmp/mapped2::1" --allowed-extensions "" --denied-extensions "" +python sftpgo_api_cli.py update-user 9576 test_username --password "test_pwd" --home-dir="/tmp/test_home_dir" --uid 0 --gid 33 --max-sessions 3 --quota-size 0 --quota-files 4 --permissions "*" --subdirs-permissions "/dir1::list,download,create_symlinks" --upload-bandwidth 90 --download-bandwidth 80 --status 1 --expiration-date "" --allowed-ip "" --denied-ip "192.168.1.0/24" --denied-login-methods "" --fs local --virtual-folders "/vdir1::/tmp/mapped1::-1::-1" "/vdir2::/tmp/mapped2::100::104857600" --allowed-extensions "" --denied-extensions "" ``` Output: @@ -203,13 +203,23 @@ Output: "username": "test_username", "virtual_folders": [ { - "exclude_from_quota": false, + "id": 1, + "last_quota_update": 0, "mapped_path": "/tmp/mapped1", + "quota_files": -1, + "quota_size": -1, + "used_quota_files": 0, + "used_quota_size": 0, "virtual_path": "/vdir1" }, { - "exclude_from_quota": true, + "id": 2, + "last_quota_update": 0, "mapped_path": "/tmp/mapped2", + "quota_files": 100, + "quota_size": 104857600, + "used_quota_files": 0, + "used_quota_size": 0, "virtual_path": "/vdir2" } ] @@ -315,6 +325,49 @@ Output: ] ``` +### Get folders + +Command: + +``` +python sftpgo_api_cli.py get-folders --limit 1 --offset 0 --folder-path /tmp/mapped1 --order DESC +``` + +Output: + +```json +[ + { + "id": 1, + "last_quota_update": 1591563422870, + "mapped_path": "/tmp/mapped1", + "used_quota_files": 1, + "used_quota_size": 13313790, + "users": [ + "test_username" + ] + } +] +``` + +### Add folder + +``` +python sftpgo_api_cli.py add-folder /tmp/mapped_folder +``` + +Output: + +```json +{ + "id": 4, + "last_quota_update": 0, + "mapped_path": "/tmp/mapped_folder", + "used_quota_files": 0, + "used_quota_size": 0 +} +``` + ### Close connection Command: @@ -359,6 +412,32 @@ Output: } ``` +### Get folder quota scans + +Command: + +``` +python sftpgo_api_cli.py get-folders-quota-scans +``` + +### Start folder quota scan + +Command: + +``` +python sftpgo_api_cli.py start-folder-quota-scan /tmp/mapped_folder +``` + +Output: + +```json +{ + "status": 201, + "message": "Scan started", + "error": "" +} +``` + ### Delete user Command: @@ -377,6 +456,22 @@ Output: } ``` +### Delete folder + +``` +python sftpgo_api_cli.py delete-folder /tmp/mapped_folder +``` + +Output: + +```json +{ + "error": "", + "message": "Folder deleted", + "status": 200 +} +``` + ### Get version Command: diff --git a/examples/rest-api-cli/sftpgo_api_cli.py b/examples/rest-api-cli/sftpgo_api_cli.py index 90e40fd8..c6d49aa5 100755 --- a/examples/rest-api-cli/sftpgo_api_cli.py +++ b/examples/rest-api-cli/sftpgo_api_cli.py @@ -32,7 +32,9 @@ class SFTPGoApiRequests: def __init__(self, debug, baseUrl, authType, authUser, authPassword, secure, no_color): self.userPath = urlparse.urljoin(baseUrl, '/api/v1/user') + self.folderPath = urlparse.urljoin(baseUrl, '/api/v1/folder') self.quotaScanPath = urlparse.urljoin(baseUrl, '/api/v1/quota_scan') + self.folderQuotaScanPath = urlparse.urljoin(baseUrl, '/api/v1/folder_quota_scan') self.activeConnectionsPath = urlparse.urljoin(baseUrl, '/api/v1/connection') self.versionPath = urlparse.urljoin(baseUrl, '/api/v1/version') self.providerStatusPath = urlparse.urljoin(baseUrl, '/api/v1/providerstatus') @@ -110,19 +112,25 @@ class SFTPGoApiRequests: if '::' in f: vpath = '' mapped_path = '' - exclude_from_quota = False + quota_files = 0 + quota_size = 0 values = f.split('::') if len(values) > 1: vpath = values[0] mapped_path = values[1] if len(values) > 2: try: - exclude_from_quota = int(values[2]) > 0 + quota_files = int(values[2]) + except: + pass + if len(values) > 3: + try: + quota_size = int(values[3]) except: pass if vpath and mapped_path: result.append({"virtual_path":vpath, "mapped_path":mapped_path, - "exclude_from_quota":exclude_from_quota}) + "quota_files":quota_files, "quota_size":quota_size}) return result def buildPermissions(self, root_perms, subdirs_perms): @@ -293,6 +301,29 @@ class SFTPGoApiRequests: r = requests.post(self.quotaScanPath, json=u, auth=self.auth, verify=self.verify) self.printResponse(r) + def getFoldersQuotaScans(self): + r = requests.get(self.folderQuotaScanPath, auth=self.auth, verify=self.verify) + self.printResponse(r) + + def startFolderQuotaScan(self, mapped_path): + f = {"mapped_path":mapped_path} + r = requests.post(self.folderQuotaScanPath, json=f, auth=self.auth, verify=self.verify) + self.printResponse(r) + + def addFolder(self, mapped_path): + f = {"mapped_path":mapped_path} + r = requests.post(self.folderPath, json=f, auth=self.auth, verify=self.verify) + self.printResponse(r) + + def deleteFolder(self, mapped_path): + r = requests.delete(self.folderPath, params={'folder_path':mapped_path}, auth=self.auth, verify=self.verify) + self.printResponse(r) + + def getFolders(self, limit=100, offset=0, order='ASC', mapped_path=''): + r = requests.get(self.folderPath, params={'limit':limit, 'offset':offset, 'order':order, + 'folder_path':mapped_path}, auth=self.auth, verify=self.verify) + self.printResponse(r) + def getVersion(self): r = requests.get(self.versionPath, auth=self.auth, verify=self.verify) self.printResponse(r) @@ -516,8 +547,8 @@ def addCommonUserArguments(parser): parser.add_argument('--subdirs-permissions', type=str, nargs='*', default=[], help='Permissions for subdirs. ' +'For example: "/somedir::list,download" "/otherdir/subdir::*" Default: %(default)s') parser.add_argument('--virtual-folders', type=str, nargs='*', default=[], help='Virtual folder mapping. For example: ' - +'"/vpath::/home/adir" "/vpath::C:\adir::1". If the optional third argument is > 0 the virtual ' - +'folder will be excluded from user quota. Ignored for non local filesystems. Default: %(default)s') + +'"/vpath::/home/adir" "/vpath::C:\adir::[quota_file]::[quota_size]". Quota parameters -1 means ' + +'included inside user quota, 0 means unlimited. Ignored for non local filesystems. Default: %(default)s') parser.add_argument('-U', '--upload-bandwidth', type=int, default=0, help='Maximum upload bandwidth as KB/s, 0 means unlimited. Default: %(default)s') parser.add_argument('-D', '--download-bandwidth', type=int, default=0, @@ -615,10 +646,29 @@ if __name__ == '__main__': parserCloseConnection = subparsers.add_parser('close-connection', help='Terminate an active SFTP/SCP connection') parserCloseConnection.add_argument('connectionID', type=str) - parserGetQuotaScans = subparsers.add_parser('get-quota-scans', help='Get the active quota scans') + parserGetQuotaScans = subparsers.add_parser('get-quota-scans', help='Get the active quota scans for users home directories') - parserStartQuotaScans = subparsers.add_parser('start-quota-scan', help='Start a new quota scan') - addCommonUserArguments(parserStartQuotaScans) + parserStartQuotaScan = subparsers.add_parser('start-quota-scan', help='Start a new user quota scan') + addCommonUserArguments(parserStartQuotaScan) + + parserGetFolderQuotaScans = subparsers.add_parser('get-folders-quota-scans', help='Get the active quota scans for folders') + + parserStartFolderQuotaScan = subparsers.add_parser('start-folder-quota-scan', help='Start a new folder quota scan') + parserStartFolderQuotaScan.add_argument('folder_path', type=str) + + parserGetFolders = subparsers.add_parser('get-folders', help='Returns an array with one or more folders') + parserGetFolders.add_argument('-L', '--limit', type=int, default=100, choices=range(1, 501), + help='Maximum allowed value is 500. Default: %(default)s', metavar='[1...500]') + parserGetFolders.add_argument('-O', '--offset', type=int, default=0, help='Default: %(default)s') + parserGetFolders.add_argument('-P', '--folder-path', type=str, default='', help='Default: %(default)s') + parserGetFolders.add_argument('-S', '--order', type=str, choices=['ASC', 'DESC'], default='ASC', + help='default: %(default)s') + + parserAddFolder = subparsers.add_parser('add-folder', help='Add a new folder') + parserAddFolder.add_argument('folder_path', type=str) + + parserDeleteFolder = subparsers.add_parser('delete-folder', help='Delete an existing folder') + parserDeleteFolder.add_argument('folder_path', type=str) parserGetVersion = subparsers.add_parser('get-version', help='Get version details') @@ -697,6 +747,16 @@ if __name__ == '__main__': api.getQuotaScans() elif args.command == 'start-quota-scan': api.startQuotaScan(args.username) + elif args.command == 'get-folders': + api.getFolders(args.limit, args.offset, args.order, args.folder_path) + elif args.command == 'add-folder': + api.addFolder(args.folder_path) + elif args.command == 'delete-folder': + api.deleteFolder(args.folder_path) + elif args.command == 'get-folders-quota-scans': + api.getFoldersQuotaScans() + elif args.command == 'start-folder-quota-scan': + api.startFolderQuotaScan(args.folder_path) elif args.command == 'get-version': api.getVersion() elif args.command == 'get-provider-status': diff --git a/httpd/api_folder.go b/httpd/api_folder.go new file mode 100644 index 00000000..3d4b159a --- /dev/null +++ b/httpd/api_folder.go @@ -0,0 +1,104 @@ +package httpd + +import ( + "errors" + "net/http" + "strconv" + + "github.com/go-chi/render" + + "github.com/drakkan/sftpgo/dataprovider" + "github.com/drakkan/sftpgo/vfs" +) + +func getFolders(w http.ResponseWriter, r *http.Request) { + var err error + limit := 100 + offset := 0 + order := dataprovider.OrderASC + folderPath := "" + if _, ok := r.URL.Query()["limit"]; ok { + limit, err = strconv.Atoi(r.URL.Query().Get("limit")) + if err != nil { + err = errors.New("Invalid limit") + sendAPIResponse(w, r, err, "", http.StatusBadRequest) + return + } + if limit > 500 { + limit = 500 + } + } + if _, ok := r.URL.Query()["offset"]; ok { + offset, err = strconv.Atoi(r.URL.Query().Get("offset")) + if err != nil { + err = errors.New("Invalid offset") + sendAPIResponse(w, r, err, "", http.StatusBadRequest) + return + } + } + if _, ok := r.URL.Query()["order"]; ok { + order = r.URL.Query().Get("order") + if order != dataprovider.OrderASC && order != dataprovider.OrderDESC { + err = errors.New("Invalid order") + sendAPIResponse(w, r, err, "", http.StatusBadRequest) + return + } + } + if _, ok := r.URL.Query()["folder_path"]; ok { + folderPath = r.URL.Query().Get("folder_path") + } + folders, err := dataprovider.GetFolders(dataProvider, limit, offset, order, folderPath) + if err == nil { + render.JSON(w, r, folders) + } else { + sendAPIResponse(w, r, err, "", http.StatusInternalServerError) + } +} + +func addFolder(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + var folder vfs.BaseVirtualFolder + err := render.DecodeJSON(r.Body, &folder) + if err != nil { + sendAPIResponse(w, r, err, "", http.StatusBadRequest) + return + } + err = dataprovider.AddFolder(dataProvider, folder) + if err == nil { + folder, err = dataprovider.GetFolderByPath(dataProvider, folder.MappedPath) + if err == nil { + render.JSON(w, r, folder) + } else { + sendAPIResponse(w, r, err, "", http.StatusInternalServerError) + } + } else { + sendAPIResponse(w, r, err, "", getRespStatus(err)) + } +} + +func deleteFolderByPath(w http.ResponseWriter, r *http.Request) { + var folderPath string + if _, ok := r.URL.Query()["folder_path"]; ok { + folderPath = r.URL.Query().Get("folder_path") + } + if len(folderPath) == 0 { + err := errors.New("a non-empty folder path is required") + sendAPIResponse(w, r, err, "", http.StatusBadRequest) + return + } + + folder, err := dataprovider.GetFolderByPath(dataProvider, folderPath) + if _, ok := err.(*dataprovider.RecordNotFoundError); ok { + sendAPIResponse(w, r, err, "", http.StatusNotFound) + return + } else if err != nil { + sendAPIResponse(w, r, err, "", http.StatusInternalServerError) + return + } + err = dataprovider.DeleteFolder(dataProvider, folder) + if err != nil { + sendAPIResponse(w, r, err, "", http.StatusInternalServerError) + } else { + sendAPIResponse(w, r, err, "Folder deleted", http.StatusOK) + } +} diff --git a/httpd/api_maintenance.go b/httpd/api_maintenance.go index b27c5758..2e8162d0 100644 --- a/httpd/api_maintenance.go +++ b/httpd/api_maintenance.go @@ -14,6 +14,7 @@ import ( "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/sftpd" + "github.com/drakkan/sftpgo/vfs" ) func dumpData(w http.ResponseWriter, r *http.Request) { @@ -45,7 +46,7 @@ func dumpData(w http.ResponseWriter, r *http.Request) { } logger.Debug(logSender, "", "dumping data to: %#v", outputFile) - users, err := dataprovider.DumpUsers(dataProvider) + backup, err := dataprovider.DumpData(dataProvider) if err != nil { logger.Warn(logSender, "", "dumping data error: %v, output file: %#v", err, outputFile) sendAPIResponse(w, r, err, "", getRespStatus(err)) @@ -53,13 +54,9 @@ func dumpData(w http.ResponseWriter, r *http.Request) { } var dump []byte if indent == "1" { - dump, err = json.MarshalIndent(dataprovider.BackupData{ - Users: users, - }, "", " ") + dump, err = json.MarshalIndent(backup, "", " ") } else { - dump, err = json.Marshal(dataprovider.BackupData{ - Users: users, - }) + dump, err = json.Marshal(backup) } if err == nil { err = ioutil.WriteFile(outputFile, dump, 0600) @@ -106,39 +103,16 @@ func loadData(w http.ResponseWriter, r *http.Request) { return } - for _, user := range dump.Users { - u, err := dataprovider.UserExists(dataProvider, user.Username) - if err == nil { - if mode == 1 { - logger.Debug(logSender, "", "loaddata mode 1, existing user %#v not updated", u.Username) - continue - } - user.ID = u.ID - user.LastLogin = u.LastLogin - user.UsedQuotaSize = u.UsedQuotaSize - user.UsedQuotaFiles = u.UsedQuotaFiles - err = dataprovider.UpdateUser(dataProvider, user) - user.Password = "[redacted]" - logger.Debug(logSender, "", "restoring existing user: %+v, dump file: %#v, error: %v", user, inputFile, err) - } else { - user.LastLogin = 0 - user.UsedQuotaSize = 0 - user.UsedQuotaFiles = 0 - err = dataprovider.AddUser(dataProvider, user) - user.Password = "[redacted]" - logger.Debug(logSender, "", "adding new user: %+v, dump file: %#v, error: %v", user, inputFile, err) - } - if err != nil { - sendAPIResponse(w, r, err, "", getRespStatus(err)) - return - } - if scanQuota == 1 || (scanQuota == 2 && user.HasQuotaRestrictions()) { - if sftpd.AddQuotaScan(user.Username) { - logger.Debug(logSender, "", "starting quota scan for restored user: %#v", user.Username) - go doQuotaScan(user) //nolint:errcheck - } - } + if err = restoreFolders(dump.Folders, inputFile, scanQuota); err != nil { + sendAPIResponse(w, r, err, "", getRespStatus(err)) + return } + + if err = restoreUsers(dump.Users, inputFile, mode, scanQuota); err != nil { + sendAPIResponse(w, r, err, "", getRespStatus(err)) + return + } + logger.Debug(logSender, "", "backup restored, users: %v", len(dump.Users)) sendAPIResponse(w, r, err, "Data restored", http.StatusOK) } @@ -165,3 +139,56 @@ func getLoaddataOptions(r *http.Request) (string, int, int, error) { } return inputFile, scanQuota, restoreMode, err } + +func restoreFolders(folders []vfs.BaseVirtualFolder, inputFile string, scanQuota int) error { + for _, folder := range folders { + _, err := dataprovider.GetFolderByPath(dataProvider, folder.MappedPath) + if err == nil { + logger.Debug(logSender, "", "folder %#v already exists, restore not needed", folder.MappedPath) + continue + } + folder.Users = nil + err = dataprovider.AddFolder(dataProvider, folder) + logger.Debug(logSender, "", "adding new folder: %+v, dump file: %#v, error: %v", folder, inputFile, err) + if err != nil { + return err + } + if scanQuota >= 1 { + if sftpd.AddVFolderQuotaScan(folder.MappedPath) { + logger.Debug(logSender, "", "starting quota scan for restored folder: %#v", folder.MappedPath) + go doFolderQuotaScan(folder) //nolint:errcheck + } + } + } + return nil +} + +func restoreUsers(users []dataprovider.User, inputFile string, mode, scanQuota int) error { + for _, user := range users { + u, err := dataprovider.UserExists(dataProvider, user.Username) + if err == nil { + if mode == 1 { + logger.Debug(logSender, "", "loaddata mode 1, existing user %#v not updated", u.Username) + continue + } + user.ID = u.ID + err = dataprovider.UpdateUser(dataProvider, user) + user.Password = "[redacted]" + logger.Debug(logSender, "", "restoring existing user: %+v, dump file: %#v, error: %v", user, inputFile, err) + } else { + err = dataprovider.AddUser(dataProvider, user) + user.Password = "[redacted]" + logger.Debug(logSender, "", "adding new user: %+v, dump file: %#v, error: %v", user, inputFile, err) + } + if err != nil { + return err + } + if scanQuota == 1 || (scanQuota == 2 && user.HasQuotaRestrictions()) { + if sftpd.AddQuotaScan(user.Username) { + logger.Debug(logSender, "", "starting quota scan for restored user: %#v", user.Username) + go doQuotaScan(user) //nolint:errcheck + } + } + } + return nil +} diff --git a/httpd/api_quota.go b/httpd/api_quota.go index b3bce523..570a1512 100644 --- a/httpd/api_quota.go +++ b/httpd/api_quota.go @@ -8,12 +8,17 @@ import ( "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/sftpd" + "github.com/drakkan/sftpgo/vfs" ) func getQuotaScans(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, sftpd.GetQuotaScans()) } +func getVFolderQuotaScans(w http.ResponseWriter, r *http.Request) { + render.JSON(w, r, sftpd.GetVFoldersQuotaScans()) +} + func startQuotaScan(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) var u dataprovider.User @@ -27,6 +32,10 @@ func startQuotaScan(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "", http.StatusNotFound) return } + if dataprovider.GetQuotaTracking() == 0 { + sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden) + return + } if sftpd.AddQuotaScan(user.Username) { go doQuotaScan(user) //nolint:errcheck sendAPIResponse(w, r, err, "Scan started", http.StatusCreated) @@ -35,6 +44,31 @@ func startQuotaScan(w http.ResponseWriter, r *http.Request) { } } +func startVFolderQuotaScan(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + var f vfs.BaseVirtualFolder + err := render.DecodeJSON(r.Body, &f) + if err != nil { + sendAPIResponse(w, r, err, "", http.StatusBadRequest) + return + } + folder, err := dataprovider.GetFolderByPath(dataProvider, f.MappedPath) + if err != nil { + sendAPIResponse(w, r, err, "", http.StatusNotFound) + return + } + if dataprovider.GetQuotaTracking() == 0 { + sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden) + return + } + if sftpd.AddVFolderQuotaScan(folder.MappedPath) { + go doFolderQuotaScan(folder) //nolint:errcheck + sendAPIResponse(w, r, err, "Scan started", http.StatusCreated) + } else { + sendAPIResponse(w, r, err, "Another scan is already in progress", http.StatusConflict) + } +} + func doQuotaScan(user dataprovider.User) error { defer sftpd.RemoveQuotaScan(user.Username) //nolint:errcheck fs, err := user.GetFilesystem("") @@ -45,9 +79,22 @@ func doQuotaScan(user dataprovider.User) error { numFiles, size, err := fs.ScanRootDirContents() if err != nil { logger.Warn(logSender, "", "error scanning user home dir %#v: %v", user.Username, err) - } else { - err = dataprovider.UpdateUserQuota(dataProvider, user, numFiles, size, true) - logger.Debug(logSender, "", "user home dir scanned, user: %#v, error: %v", user.Username, err) + return err } + err = dataprovider.UpdateUserQuota(dataProvider, user, numFiles, size, true) + logger.Debug(logSender, "", "user home dir scanned, user: %#v, error: %v", user.Username, err) + return err +} + +func doFolderQuotaScan(folder vfs.BaseVirtualFolder) error { + defer sftpd.RemoveVFolderQuotaScan(folder.MappedPath) //nolint:errcheck + fs := vfs.NewOsFs("", "", nil).(vfs.OsFs) + numFiles, size, err := fs.GetDirSize(folder.MappedPath) + if err != nil { + logger.Warn(logSender, "", "error scanning folder %#v: %v", folder.MappedPath, err) + return err + } + err = dataprovider.UpdateVirtualFolderQuota(dataProvider, folder, numFiles, size, true) + logger.Debug(logSender, "", "virtual folder %#v scanned, error: %v", folder.MappedPath, err) return err } diff --git a/httpd/api_user.go b/httpd/api_user.go index 5ff59643..254c4224 100644 --- a/httpd/api_user.go +++ b/httpd/api_user.go @@ -15,7 +15,7 @@ import ( func getUsers(w http.ResponseWriter, r *http.Request) { limit := 100 offset := 0 - order := "ASC" + order := dataprovider.OrderASC username := "" var err error if _, ok := r.URL.Query()["limit"]; ok { @@ -39,7 +39,7 @@ func getUsers(w http.ResponseWriter, r *http.Request) { } if _, ok := r.URL.Query()["order"]; ok { order = r.URL.Query().Get("order") - if order != "ASC" && order != "DESC" { + if order != dataprovider.OrderASC && order != dataprovider.OrderDESC { err = errors.New("Invalid order") sendAPIResponse(w, r, err, "", http.StatusBadRequest) return diff --git a/httpd/api_utils.go b/httpd/api_utils.go index 228ef435..845bc3ec 100644 --- a/httpd/api_utils.go +++ b/httpd/api_utils.go @@ -22,6 +22,7 @@ import ( "github.com/drakkan/sftpgo/httpclient" "github.com/drakkan/sftpgo/sftpd" "github.com/drakkan/sftpgo/utils" + "github.com/drakkan/sftpgo/vfs" ) var ( @@ -90,10 +91,7 @@ func getRespStatus(err error) int { func AddUser(user dataprovider.User, expectedStatusCode int) (dataprovider.User, []byte, error) { var newUser dataprovider.User var body []byte - userAsJSON, err := json.Marshal(user) - if err != nil { - return newUser, body, err - } + userAsJSON, _ := json.Marshal(user) resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(userPath), bytes.NewBuffer(userAsJSON), "application/json") if err != nil { @@ -120,10 +118,7 @@ func AddUser(user dataprovider.User, expectedStatusCode int) (dataprovider.User, func UpdateUser(user dataprovider.User, expectedStatusCode int) (dataprovider.User, []byte, error) { var newUser dataprovider.User var body []byte - userAsJSON, err := json.Marshal(user) - if err != nil { - return user, body, err - } + userAsJSON, _ := json.Marshal(user) resp, err := sendHTTPRequest(http.MethodPut, buildURLRelativeToBase(userPath, strconv.FormatInt(user.ID, 10)), bytes.NewBuffer(userAsJSON), "application/json") if err != nil { @@ -174,28 +169,22 @@ func GetUserByID(userID int64, expectedStatusCode int) (dataprovider.User, []byt return user, body, err } -// GetUsers allows to get a list of users and checks the received HTTP Status code against expectedStatusCode. +// GetUsers returns a list of users and checks the received HTTP Status code against expectedStatusCode. // The number of results can be limited specifying a limit. // Some results can be skipped specifying an offset. // The results can be filtered specifying a username, the username filter is an exact match -func GetUsers(limit int64, offset int64, username string, expectedStatusCode int) ([]dataprovider.User, []byte, error) { +func GetUsers(limit, offset int64, username string, expectedStatusCode int) ([]dataprovider.User, []byte, error) { var users []dataprovider.User var body []byte - url, err := url.Parse(buildURLRelativeToBase(userPath)) + url, err := addLimitAndOffsetQueryParams(buildURLRelativeToBase(userPath), limit, offset) if err != nil { return users, body, err } - q := url.Query() - if limit > 0 { - q.Add("limit", strconv.FormatInt(limit, 10)) - } - if offset > 0 { - q.Add("offset", strconv.FormatInt(offset, 10)) - } if len(username) > 0 { + q := url.Query() q.Add("username", username) + url.RawQuery = q.Encode() } - url.RawQuery = q.Encode() resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "") if err != nil { return users, body, err @@ -210,7 +199,7 @@ func GetUsers(limit int64, offset int64, username string, expectedStatusCode int return users, body, err } -// GetQuotaScans gets active quota scans and checks the received HTTP Status code against expectedStatusCode. +// GetQuotaScans gets active quota scans for users and checks the received HTTP Status code against expectedStatusCode. func GetQuotaScans(expectedStatusCode int) ([]sftpd.ActiveQuotaScan, []byte, error) { var quotaScans []sftpd.ActiveQuotaScan var body []byte @@ -231,10 +220,7 @@ func GetQuotaScans(expectedStatusCode int) ([]sftpd.ActiveQuotaScan, []byte, err // StartQuotaScan start a new quota scan for the given user and checks the received HTTP Status code against expectedStatusCode. func StartQuotaScan(user dataprovider.User, expectedStatusCode int) ([]byte, error) { var body []byte - userAsJSON, err := json.Marshal(user) - if err != nil { - return body, err - } + userAsJSON, _ := json.Marshal(user) resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(quotaScanPath), bytes.NewBuffer(userAsJSON), "") if err != nil { return body, err @@ -275,6 +261,114 @@ func CloseConnection(connectionID string, expectedStatusCode int) ([]byte, error return body, err } +// AddFolder adds a new folder and checks the received HTTP Status code against expectedStatusCode +func AddFolder(folder vfs.BaseVirtualFolder, expectedStatusCode int) (vfs.BaseVirtualFolder, []byte, error) { + var newFolder vfs.BaseVirtualFolder + var body []byte + folderAsJSON, _ := json.Marshal(folder) + resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(folderPath), bytes.NewBuffer(folderAsJSON), + "application/json") + if err != nil { + return newFolder, body, err + } + defer resp.Body.Close() + err = checkResponse(resp.StatusCode, expectedStatusCode) + if expectedStatusCode != http.StatusOK { + body, _ = getResponseBody(resp) + return newFolder, body, err + } + if err == nil { + err = render.DecodeJSON(resp.Body, &newFolder) + } else { + body, _ = getResponseBody(resp) + } + if err == nil { + err = checkFolder(&folder, &newFolder) + } + return newFolder, body, err +} + +// RemoveFolder removes an existing user and checks the received HTTP Status code against expectedStatusCode. +func RemoveFolder(folder vfs.BaseVirtualFolder, expectedStatusCode int) ([]byte, error) { + var body []byte + baseURL := buildURLRelativeToBase(folderPath) + url, err := url.Parse(baseURL) + if err != nil { + return body, err + } + q := url.Query() + q.Add("folder_path", folder.MappedPath) + url.RawQuery = q.Encode() + resp, err := sendHTTPRequest(http.MethodDelete, url.String(), nil, "") + if err != nil { + return body, err + } + defer resp.Body.Close() + body, _ = getResponseBody(resp) + return body, checkResponse(resp.StatusCode, expectedStatusCode) +} + +// GetFolders returns a list of folders and checks the received HTTP Status code against expectedStatusCode. +// The number of results can be limited specifying a limit. +// Some results can be skipped specifying an offset. +// The results can be filtered specifying a folder path, the folder path filter is an exact match +func GetFolders(limit int64, offset int64, mappedPath string, expectedStatusCode int) ([]vfs.BaseVirtualFolder, []byte, error) { + var folders []vfs.BaseVirtualFolder + var body []byte + url, err := addLimitAndOffsetQueryParams(buildURLRelativeToBase(folderPath), limit, offset) + if err != nil { + return folders, body, err + } + if len(mappedPath) > 0 { + q := url.Query() + q.Add("folder_path", mappedPath) + url.RawQuery = q.Encode() + } + resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "") + if err != nil { + return folders, body, err + } + defer resp.Body.Close() + err = checkResponse(resp.StatusCode, expectedStatusCode) + if err == nil && expectedStatusCode == http.StatusOK { + err = render.DecodeJSON(resp.Body, &folders) + } else { + body, _ = getResponseBody(resp) + } + return folders, body, err +} + +// GetFoldersQuotaScans gets active quota scans for folders and checks the received HTTP Status code against expectedStatusCode. +func GetFoldersQuotaScans(expectedStatusCode int) ([]sftpd.ActiveVirtualFolderQuotaScan, []byte, error) { + var quotaScans []sftpd.ActiveVirtualFolderQuotaScan + var body []byte + resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(quotaScanVFolderPath), nil, "") + if err != nil { + return quotaScans, body, err + } + defer resp.Body.Close() + err = checkResponse(resp.StatusCode, expectedStatusCode) + if err == nil && expectedStatusCode == http.StatusOK { + err = render.DecodeJSON(resp.Body, "aScans) + } else { + body, _ = getResponseBody(resp) + } + return quotaScans, body, err +} + +// StartFolderQuotaScan start a new quota scan for the given folder and checks the received HTTP Status code against expectedStatusCode. +func StartFolderQuotaScan(folder vfs.BaseVirtualFolder, expectedStatusCode int) ([]byte, error) { + var body []byte + folderAsJSON, _ := json.Marshal(folder) + resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(quotaScanVFolderPath), bytes.NewBuffer(folderAsJSON), "") + if err != nil { + return body, err + } + defer resp.Body.Close() + body, _ = getResponseBody(resp) + return body, checkResponse(resp.StatusCode, expectedStatusCode) +} + // GetVersion returns version details func GetVersion(expectedStatusCode int) (utils.VersionInfo, []byte, error) { var version utils.VersionInfo @@ -384,6 +478,39 @@ func getResponseBody(resp *http.Response) ([]byte, error) { return ioutil.ReadAll(resp.Body) } +func checkFolder(expected *vfs.BaseVirtualFolder, actual *vfs.BaseVirtualFolder) error { + if expected.ID <= 0 { + if actual.ID <= 0 { + return errors.New("actual folder ID must be > 0") + } + } else { + if actual.ID != expected.ID { + return errors.New("folder ID mismatch") + } + } + if expected.MappedPath != actual.MappedPath { + return errors.New("mapped path mismatch") + } + if expected.LastQuotaUpdate != actual.LastQuotaUpdate { + return errors.New("last quota update mismatch") + } + if expected.UsedQuotaSize != actual.UsedQuotaSize { + return errors.New("used quota size mismatch") + } + if expected.UsedQuotaFiles != actual.UsedQuotaFiles { + return errors.New("used quota files mismatch") + } + if len(expected.Users) != len(actual.Users) { + return errors.New("folder users mismatch") + } + for _, u := range actual.Users { + if !utils.IsStringInSlice(u, expected.Users) { + return errors.New("folder users mismatch") + } + } + return nil +} + func checkUser(expected *dataprovider.User, actual *dataprovider.User) error { if len(actual.Password) > 0 { return errors.New("User password must not be visible") @@ -634,3 +761,19 @@ func compareEqualsUserFields(expected *dataprovider.User, actual *dataprovider.U } return nil } + +func addLimitAndOffsetQueryParams(rawurl string, limit, offset int64) (*url.URL, error) { + url, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + q := url.Query() + if limit > 0 { + q.Add("limit", strconv.FormatInt(limit, 10)) + } + if offset > 0 { + q.Add("offset", strconv.FormatInt(offset, 10)) + } + url.RawQuery = q.Encode() + return url, err +} diff --git a/httpd/httpd.go b/httpd/httpd.go index a51899bd..1e0dd972 100644 --- a/httpd/httpd.go +++ b/httpd/httpd.go @@ -25,8 +25,10 @@ const ( apiPrefix = "/api/v1" activeConnectionsPath = "/api/v1/connection" quotaScanPath = "/api/v1/quota_scan" + quotaScanVFolderPath = "/api/v1/folder_quota_scan" userPath = "/api/v1/user" versionPath = "/api/v1/version" + folderPath = "/api/v1/folder" providerStatusPath = "/api/v1/providerstatus" dumpDataPath = "/api/v1/dumpdata" loadDataPath = "/api/v1/loaddata" @@ -36,6 +38,8 @@ const ( webUsersPath = "/web/users" webUserPath = "/web/user" webConnectionsPath = "/web/connections" + webFoldersPath = "/web/folders" + webFolderPath = "/web/folder" webStaticFilesPath = "/static" maxRestoreSize = 10485760 // 10 MB maxRequestSize = 1048576 // 1MB diff --git a/httpd/httpd_test.go b/httpd/httpd_test.go index a91aca47..044d09d0 100644 --- a/httpd/httpd_test.go +++ b/httpd/httpd_test.go @@ -43,14 +43,18 @@ const ( testPubKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0= nicola@p1" logSender = "APITesting" userPath = "/api/v1/user" + folderPath = "/api/v1/folder" activeConnectionsPath = "/api/v1/connection" quotaScanPath = "/api/v1/quota_scan" + quotaScanVFolderPath = "/api/v1/folder_quota_scan" versionPath = "/api/v1/version" metricsPath = "/metrics" pprofPath = "/debug/pprof/" webBasePath = "/web" webUsersPath = "/web/users" webUserPath = "/web/user" + webFoldersPath = "/web/folders" + webFolderPath = "/web/folder" webConnectionsPath = "/web/connections" configDir = ".." httpsCert = `-----BEGIN CERTIFICATE----- @@ -387,102 +391,180 @@ func TestAddUserInvalidFsConfig(t *testing.T) { func TestAddUserInvalidVirtualFolders(t *testing.T) { u := getTestUser() u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), + }, VirtualPath: "vdir", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) _, _, err := httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), + }, VirtualPath: "/", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(u.GetHomeDir(), "mapped_dir"), + }, VirtualPath: "/vdir", - MappedPath: filepath.Join(u.GetHomeDir(), "mapped_dir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: u.GetHomeDir(), + }, VirtualPath: "/vdir", - MappedPath: u.GetHomeDir(), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(u.GetHomeDir(), ".."), + }, VirtualPath: "/vdir", - MappedPath: filepath.Join(u.GetHomeDir(), ".."), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), + }, VirtualPath: "/vdir", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), + }, VirtualPath: "/vdir", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), + }, VirtualPath: "/vdir1", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), + }, VirtualPath: "/vdir2", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir", "subdir"), + }, VirtualPath: "/vdir1", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir", "subdir"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), + }, VirtualPath: "/vdir2", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), + }, VirtualPath: "/vdir1", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir", "subdir"), + }, VirtualPath: "/vdir2", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir", "subdir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), + }, VirtualPath: "/vdir1/subdir", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir2"), + }, VirtualPath: "/vdir1/../vdir1", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir2"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), + }, VirtualPath: "/vdir1/", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir2"), + }, VirtualPath: "/vdir1/subdir", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir2"), + }) + _, _, err = httpd.AddUser(u, http.StatusBadRequest) + assert.NoError(t, err) + u.VirtualFolders = nil + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), + }, + VirtualPath: "/vdir1/", + QuotaSize: -1, + QuotaFiles: 1, + }) + _, _, err = httpd.AddUser(u, http.StatusBadRequest) + assert.NoError(t, err) + u.VirtualFolders = nil + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), + }, + VirtualPath: "/vdir1/", + QuotaSize: 1, + QuotaFiles: -1, + }) + _, _, err = httpd.AddUser(u, http.StatusBadRequest) + assert.NoError(t, err) + u.VirtualFolders = nil + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), + }, + VirtualPath: "/vdir1/", + QuotaSize: -2, + QuotaFiles: 0, + }) + _, _, err = httpd.AddUser(u, http.StatusBadRequest) + assert.NoError(t, err) + u.VirtualFolders = nil + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), + }, + VirtualPath: "/vdir1/", + QuotaSize: 0, + QuotaFiles: -2, }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) @@ -530,25 +612,204 @@ func TestUpdateUser(t *testing.T) { user.UploadBandwidth = 1024 user.DownloadBandwidth = 512 user.VirtualFolders = nil + mappedPath1 := filepath.Join(os.TempDir(), "mapped_dir1") + mappedPath2 := filepath.Join(os.TempDir(), "mapped_dir2") user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, VirtualPath: "/vdir1", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), }) user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ - VirtualPath: "/vdir12/subdir", - MappedPath: filepath.Join(os.TempDir(), "mapped_dir2"), - ExcludeFromQuota: true, + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: "/vdir12/subdir", + QuotaSize: 123, + QuotaFiles: 2, }) user, _, err = httpd.UpdateUser(user, http.StatusOK) assert.NoError(t, err) user.Permissions["/subdir"] = []string{} user, _, err = httpd.UpdateUser(user, http.StatusOK) assert.NoError(t, err) - if len(user.Permissions["/subdir"]) > 0 { - t.Errorf("unexpected subdir permissions, must be empty") + assert.Len(t, user.Permissions["/subdir"], 0) + assert.Len(t, user.VirtualFolders, 2) + for _, folder := range user.VirtualFolders { + assert.Greater(t, folder.ID, int64(0)) + if folder.VirtualPath == "/vdir12/subdir" { + assert.Equal(t, int64(123), folder.QuotaSize) + assert.Equal(t, 2, folder.QuotaFiles) + } } + folder, _, err := httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Len(t, f.Users, 1) + assert.Contains(t, f.Users, user.Username) + } + _, err = httpd.RemoveUser(user, http.StatusOK) assert.NoError(t, err) + // removing the user must remove folder mapping + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Len(t, f.Users, 0) + _, err = httpd.RemoveFolder(f, http.StatusOK) + assert.NoError(t, err) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Len(t, f.Users, 0) + _, err = httpd.RemoveFolder(f, http.StatusOK) + assert.NoError(t, err) + } +} + +func TestUserFolderMapping(t *testing.T) { + mappedPath1 := filepath.Join(os.TempDir(), "mapped_dir1") + mappedPath2 := filepath.Join(os.TempDir(), "mapped_dir2") + u1 := getTestUser() + u1.VirtualFolders = append(u1.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: "/vdir", + QuotaSize: -1, + QuotaFiles: -1, + }) + user1, _, err := httpd.AddUser(u1, http.StatusOK) + assert.NoError(t, err) + // virtual folder must be auto created + folders, _, err := httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Len(t, folder.Users, 1) + assert.Contains(t, folder.Users, user1.Username) + } + u2 := getTestUser() + u2.Username = defaultUsername + "2" + u2.VirtualFolders = append(u2.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: "/vdir1", + QuotaSize: 0, + QuotaFiles: 0, + }) + u2.VirtualFolders = append(u2.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: "/vdir2", + QuotaSize: -1, + QuotaFiles: -1, + }) + user2, _, err := httpd.AddUser(u2, http.StatusOK) + assert.NoError(t, err) + folders, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Len(t, folder.Users, 1) + assert.Contains(t, folder.Users, user2.Username) + } + folders, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Len(t, folder.Users, 2) + assert.Contains(t, folder.Users, user1.Username) + assert.Contains(t, folder.Users, user2.Username) + } + // now update user2 removing mappedPath1 + user2.VirtualFolders = nil + user2.VirtualFolders = append(user2.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: "/vdir", + QuotaSize: 0, + QuotaFiles: 0, + }) + user2, _, err = httpd.UpdateUser(user2, http.StatusOK) + assert.NoError(t, err) + folders, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Len(t, folder.Users, 1) + assert.Contains(t, folder.Users, user2.Username) + } + folders, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Len(t, folder.Users, 1) + assert.Contains(t, folder.Users, user1.Username) + } + // add mappedPath1 again to user2 + user2.VirtualFolders = append(user2.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: "/vdir1", + }) + user2, _, err = httpd.UpdateUser(user2, http.StatusOK) + assert.NoError(t, err) + folders, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Len(t, folder.Users, 1) + assert.Contains(t, folder.Users, user2.Username) + } + // removing virtual folders should clear relations on both side + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, http.StatusOK) + assert.NoError(t, err) + user2, _, err = httpd.GetUserByID(user2.ID, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, user2.VirtualFolders, 1) { + folder := user2.VirtualFolders[0] + assert.Equal(t, mappedPath1, folder.MappedPath) + } + user1, _, err = httpd.GetUserByID(user1.ID, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, user2.VirtualFolders, 1) { + folder := user2.VirtualFolders[0] + assert.Equal(t, mappedPath1, folder.MappedPath) + } + + folders, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Len(t, folder.Users, 2) + } + // removing a user should clear virtual folder mapping + _, err = httpd.RemoveUser(user1, http.StatusOK) + assert.NoError(t, err) + folders, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Len(t, folder.Users, 1) + assert.Contains(t, folder.Users, user2.Username) + } + // removing a folder should clear mapping on the user side too + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + user2, _, err = httpd.GetUserByID(user2.ID, http.StatusOK) + assert.NoError(t, err) + assert.Len(t, user2.VirtualFolders, 0) + _, err = httpd.RemoveUser(user2, http.StatusOK) + assert.NoError(t, err) } func TestUserS3Config(t *testing.T) { @@ -740,7 +1001,11 @@ func TestGetQuotaScans(t *testing.T) { _, _, err := httpd.GetQuotaScans(http.StatusOK) assert.NoError(t, err) _, _, err = httpd.GetQuotaScans(http.StatusInternalServerError) - assert.Error(t, err, "quota scan request must succeed, we requested to check a wrong status code") + assert.Error(t, err) + _, _, err = httpd.GetFoldersQuotaScans(http.StatusOK) + assert.NoError(t, err) + _, _, err = httpd.GetFoldersQuotaScans(http.StatusInternalServerError) + assert.Error(t, err) } func TestStartQuotaScan(t *testing.T) { @@ -750,6 +1015,15 @@ func TestStartQuotaScan(t *testing.T) { assert.NoError(t, err) _, err = httpd.RemoveUser(user, http.StatusOK) assert.NoError(t, err) + folder := vfs.BaseVirtualFolder{ + MappedPath: filepath.Join(os.TempDir(), "folder"), + } + _, _, err = httpd.AddFolder(folder, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.StartFolderQuotaScan(folder, http.StatusCreated) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(folder, http.StatusOK) + assert.NoError(t, err) } func TestGetVersion(t *testing.T) { @@ -791,8 +1065,10 @@ func TestUserBaseDir(t *testing.T) { httpd.SetDataProvider(dataprovider.GetProvider()) u := getTestUser() u.HomeDir = "" - user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) - assert.NoError(t, err) + user, _, err := httpd.AddUser(u, http.StatusOK) + if assert.Error(t, err) { + assert.EqualError(t, err, "HomeDir mismatch") + } assert.Equal(t, filepath.Join(providerConf.UsersBaseDir, u.Username), user.HomeDir) _, err = httpd.RemoveUser(user, http.StatusOK) assert.NoError(t, err) @@ -811,6 +1087,50 @@ func TestUserBaseDir(t *testing.T) { sftpd.SetDataProvider(dataprovider.GetProvider()) } +func TestQuotaTrackingDisabled(t *testing.T) { + dataProvider := dataprovider.GetProvider() + err := dataprovider.Close(dataProvider) + assert.NoError(t, err) + err = config.LoadConfig(configDir, "") + assert.NoError(t, err) + providerConf := config.GetProviderConf() + providerConf.TrackQuota = 0 + err = dataprovider.Initialize(providerConf, configDir) + assert.NoError(t, err) + httpd.SetDataProvider(dataprovider.GetProvider()) + // user quota scan must fail + user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) + assert.NoError(t, err) + _, err = httpd.StartQuotaScan(user, http.StatusForbidden) + assert.NoError(t, err) + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + // folder quota scan must fail + folder := vfs.BaseVirtualFolder{ + MappedPath: os.TempDir(), + } + folder, _, err = httpd.AddFolder(folder, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.StartFolderQuotaScan(folder, http.StatusForbidden) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(folder, http.StatusOK) + assert.NoError(t, err) + + dataProvider = dataprovider.GetProvider() + err = dataprovider.Close(dataProvider) + assert.NoError(t, err) + err = config.LoadConfig(configDir, "") + assert.NoError(t, err) + providerConf = config.GetProviderConf() + providerConf.CredentialsPath = credentialsPath + err = os.RemoveAll(credentialsPath) + assert.NoError(t, err) + err = dataprovider.Initialize(providerConf, configDir) + assert.NoError(t, err) + httpd.SetDataProvider(dataprovider.GetProvider()) + sftpd.SetDataProvider(dataprovider.GetProvider()) +} + func TestProviderErrors(t *testing.T) { dataProvider := dataprovider.GetProvider() err := dataprovider.Close(dataProvider) @@ -823,20 +1143,32 @@ func TestProviderErrors(t *testing.T) { assert.NoError(t, err) _, err = httpd.RemoveUser(dataprovider.User{}, http.StatusInternalServerError) assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: "apath"}, http.StatusInternalServerError) + assert.NoError(t, err) _, _, err = httpd.GetProviderStatus(http.StatusInternalServerError) assert.NoError(t, err) _, _, err = httpd.Dumpdata("backup.json", "", http.StatusInternalServerError) assert.NoError(t, err) + _, _, err = httpd.GetFolders(0, 0, "", http.StatusInternalServerError) + assert.NoError(t, err) user := getTestUser() user.ID = 1 backupData := dataprovider.BackupData{} backupData.Users = append(backupData.Users, user) - backupContent, _ := json.Marshal(backupData) + backupContent, err := json.Marshal(backupData) + assert.NoError(t, err) backupFilePath := filepath.Join(backupsPath, "backup.json") err = ioutil.WriteFile(backupFilePath, backupContent, 0666) assert.NoError(t, err) _, _, err = httpd.Loaddata(backupFilePath, "", "", http.StatusInternalServerError) assert.NoError(t, err) + backupData.Folders = append(backupData.Folders, vfs.BaseVirtualFolder{MappedPath: os.TempDir()}) + backupContent, err = json.Marshal(backupData) + assert.NoError(t, err) + err = ioutil.WriteFile(backupFilePath, backupContent, 0666) + assert.NoError(t, err) + _, _, err = httpd.Loaddata(backupFilePath, "", "", http.StatusInternalServerError) + assert.NoError(t, err) err = os.Remove(backupFilePath) assert.NoError(t, err) err = config.LoadConfig(configDir, "") @@ -851,6 +1183,66 @@ func TestProviderErrors(t *testing.T) { sftpd.SetDataProvider(dataprovider.GetProvider()) } +func TestFolders(t *testing.T) { + folder := vfs.BaseVirtualFolder{ + MappedPath: "relative path", + } + _, _, err := httpd.AddFolder(folder, http.StatusBadRequest) + assert.NoError(t, err) + folder.MappedPath = os.TempDir() + folder1, _, err := httpd.AddFolder(folder, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, folder.MappedPath, folder1.MappedPath) + assert.Equal(t, 0, folder1.UsedQuotaFiles) + assert.Equal(t, int64(0), folder1.UsedQuotaSize) + assert.Equal(t, int64(0), folder1.LastQuotaUpdate) + // adding a duplicate folder must fail + _, _, err = httpd.AddFolder(folder, http.StatusOK) + assert.Error(t, err) + folder.MappedPath = filepath.Join(os.TempDir(), "vfolder") + folder.UsedQuotaFiles = 1 + folder.UsedQuotaSize = 345 + folder.LastQuotaUpdate = 10 + folder2, _, err := httpd.AddFolder(folder, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, folder2.UsedQuotaFiles) + assert.Equal(t, int64(345), folder2.UsedQuotaSize) + assert.Equal(t, int64(10), folder2.LastQuotaUpdate) + folders, _, err := httpd.GetFolders(0, 0, "", http.StatusOK) + assert.NoError(t, err) + numResults := len(folders) + assert.GreaterOrEqual(t, numResults, 2) + folders, _, err = httpd.GetFolders(0, 1, "", http.StatusOK) + assert.NoError(t, err) + assert.Len(t, folders, numResults-1) + folders, _, err = httpd.GetFolders(1, 0, "", http.StatusOK) + assert.NoError(t, err) + assert.Len(t, folders, 1) + folders, _, err = httpd.GetFolders(0, 0, folder1.MappedPath, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + f := folders[0] + assert.Equal(t, folder1.MappedPath, f.MappedPath) + } + folders, _, err = httpd.GetFolders(0, 0, folder2.MappedPath, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + f := folders[0] + assert.Equal(t, folder2.MappedPath, f.MappedPath) + } + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{}, http.StatusBadRequest) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{ + MappedPath: "invalid", + }, http.StatusNotFound) + assert.NoError(t, err) + + _, err = httpd.RemoveFolder(folder1, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(folder2, http.StatusOK) + assert.NoError(t, err) +} + func TestDumpdata(t *testing.T) { dataProvider := dataprovider.GetProvider() err := dataprovider.Close(dataProvider) @@ -901,14 +1293,28 @@ func TestDumpdata(t *testing.T) { } func TestLoaddata(t *testing.T) { + mappedPath := filepath.Join(os.TempDir(), "restored_folder") user := getTestUser() user.ID = 1 user.Username = "test_user_restore" backupData := dataprovider.BackupData{} backupData.Users = append(backupData.Users, user) - backupContent, _ := json.Marshal(backupData) + backupData.Folders = []vfs.BaseVirtualFolder{ + { + MappedPath: mappedPath, + UsedQuotaSize: 123, + UsedQuotaFiles: 456, + LastQuotaUpdate: 789, + Users: []string{"user"}, + }, + { + MappedPath: mappedPath, + }, + } + backupContent, err := json.Marshal(backupData) + assert.NoError(t, err) backupFilePath := filepath.Join(backupsPath, "backup.json") - err := ioutil.WriteFile(backupFilePath, backupContent, 0666) + err = ioutil.WriteFile(backupFilePath, backupContent, 0666) assert.NoError(t, err) _, _, err = httpd.Loaddata(backupFilePath, "a", "", http.StatusBadRequest) assert.NoError(t, err) @@ -926,7 +1332,7 @@ func TestLoaddata(t *testing.T) { err = os.Chmod(backupFilePath, 0644) assert.NoError(t, err) } - // add user from backup + // add user and folder from backup _, _, err = httpd.Loaddata(backupFilePath, "1", "", http.StatusOK) assert.NoError(t, err) // update user from backup @@ -934,11 +1340,23 @@ func TestLoaddata(t *testing.T) { assert.NoError(t, err) users, _, err := httpd.GetUsers(1, 0, user.Username, http.StatusOK) assert.NoError(t, err) - if assert.Equal(t, 1, len(users)) { + if assert.Len(t, users, 1) { user = users[0] _, err = httpd.RemoveUser(user, http.StatusOK) assert.NoError(t, err) } + folders, _, err := httpd.GetFolders(1, 0, mappedPath, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Equal(t, mappedPath, folder.MappedPath) + assert.Equal(t, int64(123), folder.UsedQuotaSize) + assert.Equal(t, 456, folder.UsedQuotaFiles) + assert.Equal(t, int64(789), folder.LastQuotaUpdate) + assert.Len(t, folder.Users, 0) + _, err = httpd.RemoveFolder(folder, http.StatusOK) + assert.NoError(t, err) + } err = os.Remove(backupFilePath) assert.NoError(t, err) err = createTestFile(backupFilePath, 10485761) @@ -1077,6 +1495,12 @@ func TestAddUserInvalidPermsMock(t *testing.T) { checkResponseCode(t, http.StatusBadRequest, rr.Code) } +func TestAddFolderInvalidJsonMock(t *testing.T) { + req, _ := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer([]byte("invalid json"))) + rr := executeRequest(req) + checkResponseCode(t, http.StatusBadRequest, rr.Code) +} + func TestAddUserInvalidJsonMock(t *testing.T) { req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer([]byte("invalid json"))) rr := executeRequest(req) @@ -1301,21 +1725,19 @@ func TestStartQuotaScanMock(t *testing.T) { rr = executeRequest(req) checkResponseCode(t, http.StatusCreated, rr.Code) - req, _ = http.NewRequest(http.MethodGet, quotaScanPath, nil) - rr = executeRequest(req) - checkResponseCode(t, http.StatusOK, rr.Code) var scans []sftpd.ActiveQuotaScan - err = render.DecodeJSON(rr.Body, &scans) - assert.NoError(t, err) - for len(scans) > 0 { + for { req, _ = http.NewRequest(http.MethodGet, quotaScanPath, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err = render.DecodeJSON(rr.Body, &scans) - if err != nil { + if !assert.NoError(t, err) { assert.Fail(t, err.Error(), "Error get active scans") break } + if len(scans) == 0 { + break + } time.Sleep(100 * time.Millisecond) } _, err = os.Stat(user.HomeDir) @@ -1327,20 +1749,19 @@ func TestStartQuotaScanMock(t *testing.T) { rr = executeRequest(req) checkResponseCode(t, http.StatusCreated, rr.Code) - req, _ = http.NewRequest(http.MethodGet, quotaScanPath, nil) - rr = executeRequest(req) - checkResponseCode(t, http.StatusOK, rr.Code) - err = render.DecodeJSON(rr.Body, &scans) - assert.NoError(t, err) - for len(scans) > 0 { + scans = nil + for { req, _ = http.NewRequest(http.MethodGet, quotaScanPath, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err = render.DecodeJSON(rr.Body, &scans) - if err != nil { + if !assert.NoError(t, err) { assert.Fail(t, err.Error(), "Error get active scans") break } + if len(scans) == 0 { + break + } time.Sleep(100 * time.Millisecond) } @@ -1351,7 +1772,66 @@ func TestStartQuotaScanMock(t *testing.T) { assert.NoError(t, err) } -func TestStartQuotaScanBadUserMock(t *testing.T) { +func TestStartFolderQuotaScanMock(t *testing.T) { + mappedPath := filepath.Join(os.TempDir(), "vfolder") + folder := vfs.BaseVirtualFolder{ + MappedPath: mappedPath, + } + folderAsJSON, err := json.Marshal(folder) + assert.NoError(t, err) + req, _ := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer(folderAsJSON)) + rr := executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + _, err = os.Stat(mappedPath) + if err == nil { + err = os.Remove(mappedPath) + assert.NoError(t, err) + } + // simulate a duplicate quota scan + sftpd.AddVFolderQuotaScan(mappedPath) + req, _ = http.NewRequest(http.MethodPost, quotaScanVFolderPath, bytes.NewBuffer(folderAsJSON)) + rr = executeRequest(req) + checkResponseCode(t, http.StatusConflict, rr.Code) + err = sftpd.RemoveVFolderQuotaScan(mappedPath) + assert.NoError(t, err) + // and now a real quota scan + _, err = os.Stat(mappedPath) + if err != nil && os.IsNotExist(err) { + err = os.MkdirAll(mappedPath, 0777) + assert.NoError(t, err) + } + req, _ = http.NewRequest(http.MethodPost, quotaScanVFolderPath, bytes.NewBuffer(folderAsJSON)) + rr = executeRequest(req) + checkResponseCode(t, http.StatusCreated, rr.Code) + var scans []sftpd.ActiveVirtualFolderQuotaScan + for { + req, _ = http.NewRequest(http.MethodGet, quotaScanVFolderPath, nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + err = render.DecodeJSON(rr.Body, &scans) + if !assert.NoError(t, err) { + assert.Fail(t, err.Error(), "Error get active folders scans") + break + } + if len(scans) == 0 { + break + } + time.Sleep(100 * time.Millisecond) + } + // cleanup + url, err := url.Parse(folderPath) + assert.NoError(t, err) + q := url.Query() + q.Add("folder_path", mappedPath) + url.RawQuery = q.Encode() + req, _ = http.NewRequest(http.MethodDelete, url.String(), nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + err = os.RemoveAll(folderPath) + assert.NoError(t, err) +} + +func TestStartQuotaScanNonExistentUserMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, quotaScanPath, bytes.NewBuffer(userAsJSON)) @@ -1359,12 +1839,74 @@ func TestStartQuotaScanBadUserMock(t *testing.T) { checkResponseCode(t, http.StatusNotFound, rr.Code) } -func TestStartQuotaScanNonExistentUserMock(t *testing.T) { +func TestStartQuotaScanBadUserMock(t *testing.T) { req, _ := http.NewRequest(http.MethodPost, quotaScanPath, bytes.NewBuffer([]byte("invalid json"))) rr := executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) } +func TestStartQuotaScanBadFolderMock(t *testing.T) { + req, _ := http.NewRequest(http.MethodPost, quotaScanVFolderPath, bytes.NewBuffer([]byte("invalid json"))) + rr := executeRequest(req) + checkResponseCode(t, http.StatusBadRequest, rr.Code) +} + +func TestStartQuotaScanNonExistentFolderMock(t *testing.T) { + folder := vfs.BaseVirtualFolder{ + MappedPath: os.TempDir(), + } + folderAsJSON, err := json.Marshal(folder) + assert.NoError(t, err) + req, _ := http.NewRequest(http.MethodPost, quotaScanVFolderPath, bytes.NewBuffer(folderAsJSON)) + rr := executeRequest(req) + checkResponseCode(t, http.StatusNotFound, rr.Code) +} + +func TestGetFoldersMock(t *testing.T) { + mappedPath := filepath.Join(os.TempDir(), "vfolder") + folder := vfs.BaseVirtualFolder{ + MappedPath: mappedPath, + } + folderAsJSON, err := json.Marshal(folder) + assert.NoError(t, err) + req, _ := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer(folderAsJSON)) + rr := executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + err = render.DecodeJSON(rr.Body, &folder) + assert.NoError(t, err) + + var folders []vfs.BaseVirtualFolder + url, err := url.Parse(folderPath + "?limit=510&offset=0&order=DESC") + assert.NoError(t, err) + q := url.Query() + q.Add("folder_path", mappedPath) + url.RawQuery = q.Encode() + req, _ = http.NewRequest(http.MethodGet, url.String(), nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + err = render.DecodeJSON(rr.Body, &folders) + assert.NoError(t, err) + assert.Len(t, folders, 1) + req, _ = http.NewRequest(http.MethodGet, folderPath+"?limit=a&offset=0&order=ASC", nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusBadRequest, rr.Code) + req, _ = http.NewRequest(http.MethodGet, folderPath+"?limit=1&offset=a&order=ASC", nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusBadRequest, rr.Code) + req, _ = http.NewRequest(http.MethodGet, folderPath+"?limit=1&offset=0&order=ASCa", nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusBadRequest, rr.Code) + + url, err = url.Parse(folderPath) + assert.NoError(t, err) + q = url.Query() + q.Add("folder_path", mappedPath) + url.RawQuery = q.Encode() + req, _ = http.NewRequest(http.MethodDelete, url.String(), nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) +} + func TestGetVersionMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, versionPath, nil) rr := executeRequest(req) @@ -1496,7 +2038,7 @@ func TestWebUserAddMock(t *testing.T) { form.Set("expiration_date", "") form.Set("permissions", "*") form.Set("sub_dirs_permissions", " /subdir::list ,download ") - form.Set("virtual_folders", fmt.Sprintf(" /vdir:: %v ::1", mappedDir)) + form.Set("virtual_folders", fmt.Sprintf(" /vdir:: %v :: 2 :: 1024", mappedDir)) form.Set("allowed_extensions", "/dir1::.jpg,.png") form.Set("denied_extensions", "/dir1::.zip") b, contentType, _ := getMultipartFormData(form, "", "") @@ -1616,18 +2158,26 @@ func TestWebUserAddMock(t *testing.T) { } else { assert.Fail(t, "user permissions must contain /somedir", "actual: %v", newUser.Permissions) } - vfolderFound := false + assert.Len(t, newUser.VirtualFolders, 1) for _, v := range newUser.VirtualFolders { - if v.VirtualPath == "/vdir" && v.MappedPath == mappedDir && v.ExcludeFromQuota == true { - vfolderFound = true - } + assert.Equal(t, v.VirtualPath, "/vdir") + assert.Equal(t, v.MappedPath, mappedDir) + assert.Equal(t, v.QuotaFiles, 2) + assert.Equal(t, v.QuotaSize, int64(1024)) } - assert.True(t, vfolderFound) extFilters := newUser.Filters.FileExtensions[0] assert.True(t, utils.IsStringInSlice(".zip", extFilters.DeniedExtensions)) req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(newUser.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) + url, err := url.Parse(folderPath) + assert.NoError(t, err) + q := url.Query() + q.Add("folder_path", mappedDir) + url.RawQuery = q.Encode() + req, _ = http.NewRequest(http.MethodDelete, url.String(), nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) } func TestWebUserUpdateMock(t *testing.T) { @@ -1878,12 +2428,113 @@ func TestWebUserGCSMock(t *testing.T) { assert.NoError(t, err) } +func TestAddWebFoldersMock(t *testing.T) { + mappedPath := os.TempDir() + form := make(url.Values) + form.Set("mapped_path", mappedPath) + req, err := http.NewRequest(http.MethodPost, webFolderPath, strings.NewReader(form.Encode())) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rr := executeRequest(req) + checkResponseCode(t, http.StatusSeeOther, rr.Code) + // adding the same folder will fail since the path must be unique + req, err = http.NewRequest(http.MethodPost, webFolderPath, strings.NewReader(form.Encode())) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + // invalid form + req, err = http.NewRequest(http.MethodPost, webFolderPath, strings.NewReader(form.Encode())) + assert.NoError(t, err) + req.Header.Set("Content-Type", "text/plain; boundary=") + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + + // now render the add folder page + req, err = http.NewRequest(http.MethodGet, webFolderPath, nil) + assert.NoError(t, err) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + + var folders []vfs.BaseVirtualFolder + url, err := url.Parse(folderPath) + assert.NoError(t, err) + q := url.Query() + q.Add("folder_path", mappedPath) + url.RawQuery = q.Encode() + req, _ = http.NewRequest(http.MethodGet, url.String(), nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + err = render.DecodeJSON(rr.Body, &folders) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder := folders[0] + assert.Equal(t, mappedPath, folder.MappedPath) + } + // cleanup + url, err = url.Parse(folderPath) + assert.NoError(t, err) + q = url.Query() + q.Add("folder_path", mappedPath) + url.RawQuery = q.Encode() + req, _ = http.NewRequest(http.MethodDelete, url.String(), nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) +} + +func TestWebFoldersMock(t *testing.T) { + mappedPath1 := filepath.Join(os.TempDir(), "vfolder1") + mappedPath2 := filepath.Join(os.TempDir(), "vfolder2") + folders := []vfs.BaseVirtualFolder{ + { + MappedPath: mappedPath1, + }, + { + MappedPath: mappedPath2, + }, + } + for _, folder := range folders { + folderAsJSON, err := json.Marshal(folder) + assert.NoError(t, err) + req, _ := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer(folderAsJSON)) + rr := executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + } + + req, err := http.NewRequest(http.MethodGet, webFoldersPath, nil) + assert.NoError(t, err) + rr := executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + req, err = http.NewRequest(http.MethodGet, webFoldersPath+"?qlimit=a", nil) + assert.NoError(t, err) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + req, err = http.NewRequest(http.MethodGet, webFoldersPath+"?qlimit=1", nil) + assert.NoError(t, err) + rr = executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + + for _, folder := range folders { + url, err := url.Parse(folderPath) + assert.NoError(t, err) + q := url.Query() + q.Add("folder_path", folder.MappedPath) + url.RawQuery = q.Encode() + req, _ := http.NewRequest(http.MethodDelete, url.String(), nil) + rr := executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) + } +} + func TestProviderClosedMock(t *testing.T) { dataProvider := dataprovider.GetProvider() dataprovider.Close(dataProvider) - req, _ := http.NewRequest(http.MethodGet, webUsersPath, nil) + req, _ := http.NewRequest(http.MethodGet, webFoldersPath, nil) rr := executeRequest(req) checkResponseCode(t, http.StatusInternalServerError, rr.Code) + req, _ = http.NewRequest(http.MethodGet, webUsersPath, nil) + rr = executeRequest(req) + checkResponseCode(t, http.StatusInternalServerError, rr.Code) req, _ = http.NewRequest(http.MethodGet, webUserPath+"/0", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusInternalServerError, rr.Code) diff --git a/httpd/internal_test.go b/httpd/internal_test.go index 0c87f8ba..9b428037 100644 --- a/httpd/internal_test.go +++ b/httpd/internal_test.go @@ -45,6 +45,43 @@ func TestCheckResponse(t *testing.T) { 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{} @@ -84,14 +121,18 @@ func TestCheckUser(t *testing.T) { assert.Error(t, err) actual.FsConfig.Provider = 0 expected.VirtualFolders = append(expected.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: os.TempDir(), + }, VirtualPath: "/vdir", - MappedPath: os.TempDir(), }) err = checkUser(expected, actual) assert.Error(t, err) actual.VirtualFolders = append(actual.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: os.TempDir(), + }, VirtualPath: "/vdir1", - MappedPath: os.TempDir(), }) err = checkUser(expected, actual) assert.Error(t, err) @@ -321,8 +362,12 @@ func TestApiCallsWithBadURL(t *testing.T) { assert.Error(t, err) _, err = RemoveUser(u, http.StatusNotFound) assert.Error(t, err) + _, err = RemoveFolder(vfs.BaseVirtualFolder{}, 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 = CloseConnection("non_existent_id", http.StatusNotFound) assert.Error(t, err) _, _, err = Dumpdata("backup.json", "", http.StatusBadRequest) @@ -352,6 +397,19 @@ func TestApiCallToNotListeningServer(t *testing.T) { 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 = GetFoldersQuotaScans(http.StatusOK) + assert.Error(t, err) _, _, err = GetConnections(http.StatusOK) assert.Error(t, err) _, err = CloseConnection("non_existent_id", http.StatusNotFound) diff --git a/httpd/router.go b/httpd/router.go index 6b2b81b1..1cf999f3 100644 --- a/httpd/router.go +++ b/httpd/router.go @@ -74,11 +74,16 @@ func initializeRouter(staticFilesPath string, profiler bool) { router.Delete(activeConnectionsPath+"/{connectionID}", handleCloseConnection) router.Get(quotaScanPath, getQuotaScans) router.Post(quotaScanPath, startQuotaScan) + router.Get(quotaScanVFolderPath, getVFolderQuotaScans) + router.Post(quotaScanVFolderPath, startVFolderQuotaScan) router.Get(userPath, getUsers) router.Post(userPath, addUser) router.Get(userPath+"/{userID}", getUserByID) router.Put(userPath+"/{userID}", updateUser) router.Delete(userPath+"/{userID}", deleteUser) + router.Get(folderPath, getFolders) + router.Post(folderPath, addFolder) + router.Delete(folderPath, deleteFolderByPath) router.Get(dumpDataPath, dumpData) router.Get(loadDataPath, loadData) router.Get(webUsersPath, handleGetWebUsers) @@ -87,6 +92,9 @@ func initializeRouter(staticFilesPath string, profiler bool) { router.Post(webUserPath, handleWebAddUserPost) router.Post(webUserPath+"/{userID}", handleWebUpdateUserPost) router.Get(webConnectionsPath, handleWebGetConnections) + router.Get(webFoldersPath, handleWebGetFolders) + router.Get(webFolderPath, handleWebAddFolderGet) + router.Post(webFolderPath, handleWebAddFolderPost) }) router.Group(func(router chi.Router) { diff --git a/httpd/schema/openapi.yaml b/httpd/schema/openapi.yaml index eb695799..6b5b3bab 100644 --- a/httpd/schema/openapi.yaml +++ b/httpd/schema/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.1 info: title: SFTPGo description: 'SFTPGo REST API' - version: 1.8.6 + version: 1.9.0 servers: - url: /api/v1 @@ -224,7 +224,7 @@ paths: get: tags: - quota - summary: Get the active quota scans + summary: Get the active quota scans for users home directories operationId: get_quota_scans responses: 200: @@ -268,8 +268,8 @@ paths: post: tags: - quota - summary: start a new quota scan - description: A quota scan update the number of files and their total size for the given user + summary: start a new user quota scan + description: A quota scan update the number of files and their total size for the specified user operationId: start_quota_scan requestBody: required: true @@ -348,6 +348,354 @@ paths: status: 500 message: "" error: "Error description if any" + /folder_quota_scan: + get: + tags: + - quota + summary: Get the active quota scans for folders + operationId: get_folders_quota_scans + responses: + 200: + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref : '#/components/schemas/FolderQuotaScan' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 401 + message: "" + error: "Error description if any" + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 403 + message: "" + error: "Error description if any" + 500: + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 500 + message: "" + error: "Error description if any" + post: + tags: + - quota + summary: start a new folder quota scan + description: A quota scan update the number of files and their total size for the specified folder + operationId: start_folder_quota_scan + requestBody: + required: true + content: + application/json: + schema: + $ref : '#/components/schemas/BaseVirtualFolder' + responses: + 201: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 201 + message: "Scan started" + error: "" + 400: + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 400 + message: "" + error: "Error description if any" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 401 + message: "" + error: "Error description if any" + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 403 + message: "" + error: "Error description if any" + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 404 + message: "" + error: "Error description if any" + 409: + description: Another scan is already in progress for this user + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 409 + message: "Another scan is already in progress" + error: "Error description if any" + 500: + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 500 + message: "" + error: "Error description if any" + /folder: + get: + tags: + - folders + summary: Returns an array with one or more folders + operationId: get_folders + parameters: + - in: query + name: offset + schema: + type: integer + minimum: 0 + default: 0 + required: false + - in: query + name: limit + schema: + type: integer + minimum: 1 + maximum: 500 + default: 100 + required: false + description: The maximum number of items to return. Max value is 500, default is 100 + - in: query + name: order + required: false + description: Ordering folders by path. Default ASC + schema: + type: string + enum: + - ASC + - DESC + example: ASC + - in: query + name: folder_path + required: false + description: Filter by folder path, extact match case sensitive + schema: + type: string + responses: + 200: + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref : '#/components/schemas/BaseVirtualFolder' + 400: + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 400 + message: "" + error: "Error description if any" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 401 + message: "" + error: "Error description if any" + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 403 + message: "" + error: "Error description if any" + 500: + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 500 + message: "" + error: "Error description if any" + post: + tags: + - folders + summary: Adds a new folder + operationId: add_folder + description: a new folder with the specified mapped_path will be added. To update the used quota parameters a quota scan is needed + requestBody: + required: true + content: + application/json: + schema: + $ref : '#/components/schemas/BaseVirtualFolder' + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref : '#/components/schemas/BaseVirtualFolder' + 400: + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 400 + message: "" + error: "Error description if any" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 401 + message: "" + error: "Error description if any" + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 403 + message: "" + error: "Error description if any" + 500: + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 500 + message: "" + error: "Error description if any" + delete: + tags: + - folders + summary: Delete an existing folder + operationId: delete_folder + parameters: + - name: folder_path + in: query + description: path to the folder to delete + required: true + schema: + type: string + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref : '#/components/schemas/ApiResponse' + example: + status: 200 + message: "Folder deleted" + error: "" + 400: + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 400 + message: "" + error: "Error description if any" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 401 + message: "" + error: "Error description if any" + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 403 + message: "" + error: "Error description if any" + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 404 + message: "" + error: "Error description if any" + 500: + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 500 + message: "" + error: "Error description if any" /user: get: tags: @@ -375,7 +723,7 @@ paths: - in: query name: order required: false - description: Ordering users by username + description: Ordering users by username. Default ASC schema: type: string enum: @@ -802,7 +1150,7 @@ paths: tags: - maintenance summary: Restore SFTPGo data from a JSON backup - description: Users will be restored one by one and the restore is stopped if a user cannot be added or updated, so it could happen a partial restore + description: Users and folders will be restored one by one and the restore is stopped if a user/folder cannot be added or updated, so it could happen a partial restore operationId: loaddata parameters: - in: query @@ -821,7 +1169,7 @@ paths: - 2 description: > Quota scan: - * `0` no quota scan is done, the imported user will have used_quota_size and used_quota_file = 0. This is the default + * `0` no quota scan is done, the imported users will have used_quota_size and used_quota_files = 0 or the existing values if they already exists. This is the default * `1` scan quota * `2` scan quota if the user has quota restrictions required: false @@ -1070,20 +1418,52 @@ components: gcsconfig: $ref: '#/components/schemas/GCSConfig' description: Storage filesystem details - VirtualFolder: + BaseVirtualFolder: type: object properties: - virtual_path: - type: string + id: + type: integer + format: int32 + minimum: 1 mapped_path: type: string - exclude_from_quota: - type: boolean + description: absolute filesystem path to use as virtual folder. This field is unique + used_quota_size: + type: integer + format: int64 + used_quota_files: + type: integer + format: int32 + last_quota_update: + type: integer + format: int64 + description: Last quota update as unix timestamp in milliseconds + users: + type: array nullable: true - description: This folder will be excluded from user quota + items: + type: string + description: list of usernames associated with this virtual folder required: - - virtual_path - mapped_path + description: defines the path for the virtual folder and the used quota limits. The same folder can be shared among multiple users and each user can have different quota limits or a different virtual path. + VirtualFolder: + allOf: + - $ref: '#/components/schemas/BaseVirtualFolder' + - type: object + properties: + virtual_path: + type: string + quota_size: + type: integer + format: int64 + description: Quota as size in bytes. 0 menas unlimited, -1 means included in user quota. Please note that quota is updated if files are added/removed via SFTP/SCP otherwise a quota scan is needed + quota_files: + type: integer + format: int32 + description: Quota as number of files. 0 menas unlimited, , -1 means included in user quota. Please note that quota is updated if files are added/removed via SFTP/SCP otherwise a quota scan is needed + required: + - virtual_path description: A virtual folder is a mapping between a SFTP/SCP virtual path and a filesystem path outside the user home directory. The specified paths must be absolute and the virtual path cannot be "/", it must be a sub directory. The parent directory for the specified virtual path must exist. SFTPGo will try to automatically create any missing parent directory for the configured virtual folders at user login. User: type: object @@ -1103,6 +1483,7 @@ components: * `1` user is enabled username: type: string + description: username is unique expiration_date: type: integer format: int64 @@ -1125,7 +1506,7 @@ components: items: $ref: '#/components/schemas/VirtualFolder' nullable: true - description: mapping between virtual SFTP/SCP paths and filesystem paths outside the user home directory. Supported for local filesystem only + description: mapping between virtual SFTP/SCP paths and filesystem paths outside the user home directory. Supported for local filesystem only. If one or more of the specified folders are not inside the dataprovider they will be automatically created. You have to create the folder on the filesystem yourself uid: type: integer format: int32 @@ -1159,7 +1540,7 @@ components: used_quota_size: type: integer format: int64 - used_quota_file: + used_quota_files: type: integer format: int32 last_quota_update: @@ -1251,6 +1632,16 @@ components: type: integer format: int64 description: scan start time as unix timestamp in milliseconds + FolderQuotaScan: + type: object + properties: + mapped_path: + type: string + description: path with an active scan + start_time: + type: integer + format: int64 + description: scan start time as unix timestamp in milliseconds ApiResponse: type: object properties: @@ -1258,7 +1649,7 @@ components: type: integer format: int32 minimum: 200 - maximum: 500 + maximum: 509 example: 200 description: HTTP Status code, for example 200 OK, 400 Bad request and so on message: diff --git a/httpd/web.go b/httpd/web.go index b0acbaae..ed3a7d6b 100644 --- a/httpd/web.go +++ b/httpd/web.go @@ -22,20 +22,23 @@ import ( ) const ( - templateBase = "base.html" - templateUsers = "users.html" - templateUser = "user.html" - templateConnections = "connections.html" - templateMessage = "message.html" - pageUsersTitle = "Users" - pageConnectionsTitle = "Connections" - page400Title = "Bad request" - page404Title = "Not found" - page404Body = "The page you are looking for does not exist." - page500Title = "Internal Server Error" - page500Body = "The server is unable to fulfill your request." - defaultUsersQueryLimit = 500 - webDateTimeFormat = "2006-01-02 15:04:05" // YYYY-MM-DD HH:MM:SS + templateBase = "base.html" + templateUsers = "users.html" + templateUser = "user.html" + templateConnections = "connections.html" + templateFolders = "folders.html" + templateFolder = "folder.html" + templateMessage = "message.html" + pageUsersTitle = "Users" + pageConnectionsTitle = "Connections" + pageFoldersTitle = "Folders" + page400Title = "Bad request" + page404Title = "Not found" + page404Body = "The page you are looking for does not exist." + page500Title = "Internal Server Error" + page500Body = "The server is unable to fulfill your request." + defaultQueryLimit = 500 + webDateTimeFormat = "2006-01-02 15:04:05" // YYYY-MM-DD HH:MM:SS ) var ( @@ -43,17 +46,22 @@ var ( ) type basePage struct { - Title string - CurrentURL string - UsersURL string - UserURL string - APIUserURL string - APIConnectionsURL string - APIQuotaScanURL string - ConnectionsURL string - UsersTitle string - ConnectionsTitle string - Version string + Title string + CurrentURL string + UsersURL string + UserURL string + APIUserURL string + APIConnectionsURL string + APIQuotaScanURL string + ConnectionsURL string + FoldersURL string + FolderURL string + APIFoldersURL string + APIFolderQuotaScanURL string + UsersTitle string + ConnectionsTitle string + FoldersTitle string + Version string } type usersPage struct { @@ -61,6 +69,11 @@ type usersPage struct { Users []dataprovider.User } +type foldersPage struct { + basePage + Folders []vfs.BaseVirtualFolder +} + type connectionsPage struct { basePage Connections []sftpd.ConnectionStatus @@ -77,6 +90,12 @@ type userPage struct { RootDirPerms []string } +type folderPage struct { + basePage + Folder vfs.BaseVirtualFolder + Error string +} + type messagePage struct { basePage Error string @@ -100,31 +119,48 @@ func loadTemplates(templatesPath string) { filepath.Join(templatesPath, templateBase), filepath.Join(templatesPath, templateMessage), } + foldersPath := []string{ + filepath.Join(templatesPath, templateBase), + filepath.Join(templatesPath, templateFolders), + } + folderPath := []string{ + filepath.Join(templatesPath, templateBase), + filepath.Join(templatesPath, templateFolder), + } usersTmpl := utils.LoadTemplate(template.ParseFiles(usersPaths...)) userTmpl := utils.LoadTemplate(template.ParseFiles(userPaths...)) connectionsTmpl := utils.LoadTemplate(template.ParseFiles(connectionsPaths...)) messageTmpl := utils.LoadTemplate(template.ParseFiles(messagePath...)) + foldersTmpl := utils.LoadTemplate(template.ParseFiles(foldersPath...)) + folderTmpl := utils.LoadTemplate(template.ParseFiles(folderPath...)) templates[templateUsers] = usersTmpl templates[templateUser] = userTmpl templates[templateConnections] = connectionsTmpl templates[templateMessage] = messageTmpl + templates[templateFolders] = foldersTmpl + templates[templateFolder] = folderTmpl } func getBasePageData(title, currentURL string) basePage { version := utils.GetAppVersion() return basePage{ - Title: title, - CurrentURL: currentURL, - UsersURL: webUsersPath, - UserURL: webUserPath, - APIUserURL: userPath, - APIConnectionsURL: activeConnectionsPath, - APIQuotaScanURL: quotaScanPath, - ConnectionsURL: webConnectionsPath, - UsersTitle: pageUsersTitle, - ConnectionsTitle: pageConnectionsTitle, - Version: version.GetVersionAsString(), + Title: title, + CurrentURL: currentURL, + UsersURL: webUsersPath, + UserURL: webUserPath, + FoldersURL: webFoldersPath, + FolderURL: webFolderPath, + APIUserURL: userPath, + APIConnectionsURL: activeConnectionsPath, + APIQuotaScanURL: quotaScanPath, + APIFoldersURL: folderPath, + APIFolderQuotaScanURL: quotaScanVFolderPath, + ConnectionsURL: webConnectionsPath, + UsersTitle: pageUsersTitle, + ConnectionsTitle: pageConnectionsTitle, + FoldersTitle: pageFoldersTitle, + Version: version.GetVersionAsString(), } } @@ -190,6 +226,15 @@ func renderUpdateUserPage(w http.ResponseWriter, user dataprovider.User, error s renderTemplate(w, templateUser, data) } +func renderAddFolderPage(w http.ResponseWriter, folder vfs.BaseVirtualFolder, error string) { + data := folderPage{ + basePage: getBasePageData("Add a new folder", webFolderPath), + Error: error, + Folder: folder, + } + renderTemplate(w, templateFolder, data) +} + func getVirtualFoldersFromPostFields(r *http.Request) []vfs.VirtualFolder { var virtualFolders []vfs.VirtualFolder formValue := r.Form.Get("virtual_folders") @@ -198,13 +243,23 @@ func getVirtualFoldersFromPostFields(r *http.Request) []vfs.VirtualFolder { mapping := strings.Split(cleaned, "::") if len(mapping) > 1 { vfolder := vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: strings.TrimSpace(mapping[1]), + }, VirtualPath: strings.TrimSpace(mapping[0]), - MappedPath: strings.TrimSpace(mapping[1]), + QuotaFiles: -1, + QuotaSize: -1, } if len(mapping) > 2 { - excludeFromQuota, err := strconv.Atoi(strings.TrimSpace(mapping[2])) + quotaFiles, err := strconv.Atoi(strings.TrimSpace(mapping[2])) if err == nil { - vfolder.ExcludeFromQuota = (excludeFromQuota > 0) + vfolder.QuotaFiles = quotaFiles + } + } + if len(mapping) > 3 { + quotaSize, err := strconv.ParseInt(strings.TrimSpace(mapping[3]), 10, 64) + if err == nil { + vfolder.QuotaSize = quotaSize } } virtualFolders = append(virtualFolders, vfolder) @@ -453,29 +508,26 @@ func getUserFromPostFields(r *http.Request) (dataprovider.User, error) { } func handleGetWebUsers(w http.ResponseWriter, r *http.Request) { - limit := defaultUsersQueryLimit + limit := defaultQueryLimit if _, ok := r.URL.Query()["qlimit"]; ok { var err error limit, err = strconv.Atoi(r.URL.Query().Get("qlimit")) if err != nil { - limit = defaultUsersQueryLimit + limit = defaultQueryLimit } } - var users []dataprovider.User - u, err := dataprovider.GetUsers(dataProvider, limit, 0, "ASC", "") - users = append(users, u...) - for len(u) == limit { - u, err = dataprovider.GetUsers(dataProvider, limit, len(users), "ASC", "") - if err == nil && len(u) > 0 { - users = append(users, u...) - } else { + users := make([]dataprovider.User, 0, limit) + for { + u, err := dataprovider.GetUsers(dataProvider, limit, len(users), dataprovider.OrderASC, "") + if err != nil { + renderInternalServerErrorPage(w, err) + return + } + users = append(users, u...) + if len(u) < limit { break } } - if err != nil { - renderInternalServerErrorPage(w, err) - return - } data := usersPage{ basePage: getBasePageData(pageUsersTitle, webUsersPath), Users: users, @@ -558,3 +610,54 @@ func handleWebGetConnections(w http.ResponseWriter, r *http.Request) { } renderTemplate(w, templateConnections, data) } + +func handleWebAddFolderGet(w http.ResponseWriter, r *http.Request) { + renderAddFolderPage(w, vfs.BaseVirtualFolder{}, "") +} + +func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + folder := vfs.BaseVirtualFolder{} + err := r.ParseForm() + if err != nil { + renderAddFolderPage(w, folder, err.Error()) + return + } + folder.MappedPath = r.Form.Get("mapped_path") + + err = dataprovider.AddFolder(dataProvider, folder) + if err == nil { + http.Redirect(w, r, webFoldersPath, http.StatusSeeOther) + } else { + renderAddFolderPage(w, folder, err.Error()) + } +} + +func handleWebGetFolders(w http.ResponseWriter, r *http.Request) { + limit := defaultQueryLimit + if _, ok := r.URL.Query()["qlimit"]; ok { + var err error + limit, err = strconv.Atoi(r.URL.Query().Get("qlimit")) + if err != nil { + limit = defaultQueryLimit + } + } + folders := make([]vfs.BaseVirtualFolder, 0, limit) + for { + f, err := dataprovider.GetFolders(dataProvider, limit, len(folders), dataprovider.OrderASC, "") + if err != nil { + renderInternalServerErrorPage(w, err) + return + } + folders = append(folders, f...) + if len(f) < limit { + break + } + } + + data := foldersPage{ + basePage: getBasePageData(pageFoldersTitle, webFoldersPath), + Folders: folders, + } + renderTemplate(w, templateFolders, data) +} diff --git a/sftpd/handler.go b/sftpd/handler.go index a6277452..be39306f 100644 --- a/sftpd/handler.go +++ b/sftpd/handler.go @@ -69,25 +69,25 @@ func (c Connection) Fileread(request *sftp.Request) (io.ReaderAt, error) { c.Log(logger.LevelDebug, logSender, "fileread requested for path: %#v", p) transfer := Transfer{ - file: file, - readerAt: r, - writerAt: nil, - cancelFn: cancelFn, - path: p, - start: time.Now(), - bytesSent: 0, - bytesReceived: 0, - user: c.User, - connectionID: c.ID, - transferType: transferDownload, - lastActivity: time.Now(), - isNewFile: false, - protocol: c.protocol, - transferError: nil, - isFinished: false, - minWriteOffset: 0, - isExcludedFromQuota: c.User.IsFileExcludedFromQuota(request.Filepath), - lock: new(sync.Mutex), + file: file, + readerAt: r, + writerAt: nil, + cancelFn: cancelFn, + path: p, + start: time.Now(), + bytesSent: 0, + bytesReceived: 0, + user: c.User, + connectionID: c.ID, + transferType: transferDownload, + lastActivity: time.Now(), + isNewFile: false, + protocol: c.protocol, + transferError: nil, + isFinished: false, + minWriteOffset: 0, + requestPath: request.Filepath, + lock: new(sync.Mutex), } addTransfer(&transfer) return &transfer, nil @@ -112,12 +112,12 @@ func (c Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) { filePath = c.fs.GetAtomicUploadPath(p) } - stat, statErr := c.fs.Stat(p) - if c.fs.IsNotExist(statErr) { + stat, statErr := c.fs.Lstat(p) + if (statErr == nil && stat.Mode()&os.ModeSymlink == os.ModeSymlink) || c.fs.IsNotExist(statErr) { if !c.User.HasPerm(dataprovider.PermUpload, path.Dir(request.Filepath)) { return nil, sftp.ErrSSHFxPermissionDenied } - return c.handleSFTPUploadToNewFile(p, filePath, c.User.IsFileExcludedFromQuota(request.Filepath)) + return c.handleSFTPUploadToNewFile(p, filePath, request.Filepath) } if statErr != nil { @@ -135,8 +135,7 @@ func (c Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) { return nil, sftp.ErrSSHFxPermissionDenied } - return c.handleSFTPUploadToExistingFile(request.Pflags(), p, filePath, stat.Size(), - c.User.IsFileExcludedFromQuota(request.Filepath)) + return c.handleSFTPUploadToExistingFile(request.Pflags(), p, filePath, stat.Size(), request.Filepath) } // Filecmd hander for basic SFTP system calls related to files, but not anything to do with reading @@ -301,29 +300,46 @@ func (c Connection) handleSFTPSetstat(filePath string, request *sftp.Request) er return nil } -func (c Connection) handleSFTPRename(sourcePath string, targetPath string, request *sftp.Request) error { - if c.fs.GetRelativePath(sourcePath) == "/" { - c.Log(logger.LevelWarn, logSender, "renaming root dir is not allowed") +func (c Connection) handleSFTPRename(sourcePath, targetPath string, request *sftp.Request) error { + if !c.isRenamePermitted(sourcePath, request) { return sftp.ErrSSHFxPermissionDenied } - if c.User.IsVirtualFolder(request.Filepath) || c.User.IsVirtualFolder(request.Target) { - c.Log(logger.LevelWarn, logSender, "renaming a virtual folder is not allowed") - return sftp.ErrSSHFxPermissionDenied + if c.User.HasVirtualFoldersInside(request.Filepath) { + if fi, err := c.fs.Stat(sourcePath); err == nil { + if fi.IsDir() { + c.Log(logger.LevelDebug, logSender, "renaming the folder %#v is not supported: it has virtual folders inside it", + request.Filepath) + return sftp.ErrSSHFxOpUnsupported + } + } } - if !c.User.IsFileAllowed(request.Filepath) || !c.User.IsFileAllowed(request.Target) { - if fi, err := c.fs.Lstat(sourcePath); err == nil && fi.Mode().IsRegular() { - c.Log(logger.LevelDebug, logSender, "renaming file is not allowed, source: %#v target: %#v", request.Filepath, - request.Target) + initialSize := int64(-1) + if fi, err := c.fs.Lstat(targetPath); err == nil { + if fi.IsDir() { + c.Log(logger.LevelWarn, logSender, "attempted to rename %#v overwriting an existing directory %#v", sourcePath, targetPath) + return sftp.ErrSSHFxOpUnsupported + } + // we are overwriting an existing file/symlink + if fi.Mode().IsRegular() { + initialSize = fi.Size() + } + if !c.User.HasPerm(dataprovider.PermOverwrite, path.Dir(request.Target)) { + c.Log(logger.LevelDebug, logSender, "renaming is not allowed, source: %#v target: %#v. "+ + "Target exists but the user has no overwrite permission", request.Filepath, request.Target) return sftp.ErrSSHFxPermissionDenied } } - if !c.User.HasPerm(dataprovider.PermRename, path.Dir(request.Target)) { - return sftp.ErrSSHFxPermissionDenied + if !c.hasSpaceForRename(request, initialSize, sourcePath) { + c.Log(logger.LevelInfo, logSender, "denying cross rename due to space limit") + return sftp.ErrSSHFxFailure } if err := c.fs.Rename(sourcePath, targetPath); err != nil { - c.Log(logger.LevelWarn, logSender, "failed to rename file, source: %#v target: %#v: %+v", sourcePath, targetPath, err) + c.Log(logger.LevelWarn, logSender, "failed to rename %#v -> %#v: %+v", sourcePath, targetPath, err) return vfs.GetSFTPError(c.fs, err) } + if dataprovider.GetQuotaTracking() > 0 { + c.updateQuotaAfterRename(request, targetPath, initialSize) //nolint:errcheck + } logger.CommandLog(renameLogSender, sourcePath, targetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "") // the returned error is used in test cases only, we already log the error inside executeAction go executeAction(newActionNotification(c.User, operationRename, sourcePath, targetPath, "", 0, nil)) //nolint:errcheck @@ -339,6 +355,10 @@ func (c Connection) handleSFTPRmdir(dirPath string, request *sftp.Request) error c.Log(logger.LevelWarn, logSender, "removing a virtual folder is not allowed: %#v", request.Filepath) return sftp.ErrSSHFxPermissionDenied } + if c.User.HasVirtualFoldersInside(request.Filepath) { + c.Log(logger.LevelWarn, logSender, "removing a directory with a virtual folder inside is not allowed: %#v", request.Filepath) + return sftp.ErrSSHFxOpUnsupported + } if !c.User.HasPerm(dataprovider.PermDelete, path.Dir(request.Filepath)) { return sftp.ErrSSHFxPermissionDenied } @@ -375,11 +395,14 @@ func (c Connection) handleSFTPSymlink(sourcePath string, targetPath string, requ if !c.User.HasPerm(dataprovider.PermCreateSymlinks, path.Dir(request.Target)) { return sftp.ErrSSHFxPermissionDenied } + if c.isCrossFoldersRequest(request) { + c.Log(logger.LevelWarn, logSender, "cross folder symlink is not supported, src: %v dst: %v", request.Filepath, request.Target) + return sftp.ErrSSHFxFailure + } if err := c.fs.Symlink(sourcePath, targetPath); err != nil { c.Log(logger.LevelWarn, logSender, "failed to create symlink %#v -> %#v: %+v", sourcePath, targetPath, err) return vfs.GetSFTPError(c.fs, err) } - logger.CommandLog(symlinkLogSender, sourcePath, targetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "") return nil } @@ -437,7 +460,13 @@ func (c Connection) handleSFTPRemove(filePath string, request *sftp.Request) err logger.CommandLog(removeLogSender, filePath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "") if fi.Mode()&os.ModeSymlink != os.ModeSymlink { - if !c.User.IsFileExcludedFromQuota(request.Filepath) { + vfolder, err := c.User.GetVirtualFolderForPath(request.Filepath) + if err == nil { + dataprovider.UpdateVirtualFolderQuota(dataProvider, vfolder.BaseVirtualFolder, -1, -size, false) //nolint:errcheck + if vfolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.User, -1, -size, false) //nolint:errcheck + } + } else { dataprovider.UpdateUserQuota(dataProvider, c.User, -1, -size, false) //nolint:errcheck } } @@ -448,50 +477,50 @@ func (c Connection) handleSFTPRemove(filePath string, request *sftp.Request) err return sftp.ErrSSHFxOk } -func (c Connection) handleSFTPUploadToNewFile(requestPath, filePath string, isExcludedFromQuota bool) (io.WriterAt, error) { - if !c.hasSpace(true) { - c.Log(logger.LevelInfo, logSender, "denying file write due to space limit") +func (c Connection) handleSFTPUploadToNewFile(resolvedPath, filePath, requestPath string) (io.WriterAt, error) { + if !c.hasSpace(true, requestPath) { + c.Log(logger.LevelInfo, logSender, "denying file write due to quota limits") return nil, sftp.ErrSSHFxFailure } file, w, cancelFn, err := c.fs.Create(filePath, 0) if err != nil { - c.Log(logger.LevelWarn, logSender, "error creating file %#v: %+v", requestPath, err) + c.Log(logger.LevelWarn, logSender, "error creating file %#v: %+v", resolvedPath, err) return nil, vfs.GetSFTPError(c.fs, err) } vfs.SetPathPermissions(c.fs, filePath, c.User.GetUID(), c.User.GetGID()) transfer := Transfer{ - file: file, - writerAt: w, - readerAt: nil, - cancelFn: cancelFn, - path: requestPath, - start: time.Now(), - bytesSent: 0, - bytesReceived: 0, - user: c.User, - connectionID: c.ID, - transferType: transferUpload, - lastActivity: time.Now(), - isNewFile: true, - protocol: c.protocol, - transferError: nil, - isFinished: false, - minWriteOffset: 0, - isExcludedFromQuota: isExcludedFromQuota, - lock: new(sync.Mutex), + file: file, + writerAt: w, + readerAt: nil, + cancelFn: cancelFn, + path: resolvedPath, + start: time.Now(), + bytesSent: 0, + bytesReceived: 0, + user: c.User, + connectionID: c.ID, + transferType: transferUpload, + lastActivity: time.Now(), + isNewFile: true, + protocol: c.protocol, + transferError: nil, + isFinished: false, + minWriteOffset: 0, + requestPath: requestPath, + lock: new(sync.Mutex), } addTransfer(&transfer) return &transfer, nil } -func (c Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, requestPath, filePath string, - fileSize int64, isExcludedFromQuota bool) (io.WriterAt, error) { +func (c Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, resolvedPath, filePath string, + fileSize int64, requestPath string) (io.WriterAt, error) { var err error - if !c.hasSpace(false) { - c.Log(logger.LevelInfo, logSender, "denying file write due to space limit") + if !c.hasSpace(false, requestPath) { + c.Log(logger.LevelInfo, logSender, "denying file write due to quota limits") return nil, sftp.ErrSSHFxFailure } @@ -499,16 +528,15 @@ func (c Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, re osFlags := getOSOpenFlags(pflags) if pflags.Append && osFlags&os.O_TRUNC == 0 && !c.fs.IsUploadResumeSupported() { - c.Log(logger.LevelInfo, logSender, "upload resume requested for path: %#v but not supported in fs implementation", - requestPath) + c.Log(logger.LevelInfo, logSender, "upload resume requested for path: %#v but not supported in fs implementation", resolvedPath) return nil, sftp.ErrSSHFxOpUnsupported } if isAtomicUploadEnabled() && c.fs.IsAtomicUploadSupported() { - err = c.fs.Rename(requestPath, filePath) + err = c.fs.Rename(resolvedPath, filePath) if err != nil { c.Log(logger.LevelWarn, logSender, "error renaming existing file for atomic upload, source: %#v, dest: %#v, err: %+v", - requestPath, filePath, err) + resolvedPath, filePath, err) return nil, vfs.GetSFTPError(c.fs, err) } } @@ -525,7 +553,13 @@ func (c Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, re minWriteOffset = fileSize } else { if vfs.IsLocalOsFs(c.fs) { - if !isExcludedFromQuota { + vfolder, err := c.User.GetVirtualFolderForPath(requestPath) + if err == nil { + dataprovider.UpdateVirtualFolderQuota(dataProvider, vfolder.BaseVirtualFolder, 0, -fileSize, false) //nolint:errcheck + if vfolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.User, 0, -fileSize, false) //nolint:errcheck + } + } else { dataprovider.UpdateUserQuota(dataProvider, c.User, 0, -fileSize, false) //nolint:errcheck } } else { @@ -536,48 +570,104 @@ func (c Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, re vfs.SetPathPermissions(c.fs, filePath, c.User.GetUID(), c.User.GetGID()) transfer := Transfer{ - file: file, - writerAt: w, - readerAt: nil, - cancelFn: cancelFn, - path: requestPath, - start: time.Now(), - bytesSent: 0, - bytesReceived: 0, - user: c.User, - connectionID: c.ID, - transferType: transferUpload, - lastActivity: time.Now(), - isNewFile: false, - protocol: c.protocol, - transferError: nil, - isFinished: false, - minWriteOffset: minWriteOffset, - initialSize: initialSize, - isExcludedFromQuota: isExcludedFromQuota, - lock: new(sync.Mutex), + file: file, + writerAt: w, + readerAt: nil, + cancelFn: cancelFn, + path: resolvedPath, + start: time.Now(), + bytesSent: 0, + bytesReceived: 0, + user: c.User, + connectionID: c.ID, + transferType: transferUpload, + lastActivity: time.Now(), + isNewFile: false, + protocol: c.protocol, + transferError: nil, + isFinished: false, + minWriteOffset: minWriteOffset, + initialSize: initialSize, + requestPath: requestPath, + lock: new(sync.Mutex), } addTransfer(&transfer) return &transfer, nil } -func (c Connection) hasSpace(checkFiles bool) bool { - if (checkFiles && c.User.QuotaFiles > 0) || c.User.QuotaSize > 0 { - numFile, size, err := dataprovider.GetUsedQuota(dataProvider, c.User.Username) - if err != nil { - if _, ok := err.(*dataprovider.MethodDisabledError); ok { - c.Log(logger.LevelWarn, logSender, "quota enforcement not possible for user %#v: %v", c.User.Username, err) +func (c Connection) hasSpaceForRename(request *sftp.Request, initialSize int64, sourcePath string) bool { + if dataprovider.GetQuotaTracking() == 0 { + return true + } + sourceFolder, errSrc := c.User.GetVirtualFolderForPath(request.Filepath) + dstFolder, errDst := c.User.GetVirtualFolderForPath(request.Target) + if errSrc != nil && errDst != nil { + // rename inside the user home dir + return true + } + if errSrc == nil && errDst == nil { + // rename between virtual folders + if sourceFolder.MappedPath == dstFolder.MappedPath { + // rename inside the same virtual folder + return true + } + } + if errSrc != nil && dstFolder.IsIncludedInUserQuota() { + // rename between user root dir and a virtual folder included in user quota + return true + } + if !c.hasSpace(true, request.Target) { + if initialSize != -1 { + // we are overquota but we are overwriting a file so we check the quota size + if c.hasSpace(false, request.Target) { + // we have enough quota size return true } - c.Log(logger.LevelWarn, logSender, "error getting used quota for %#v: %v", c.User.Username, err) - return false + if fi, err := c.fs.Lstat(sourcePath); err == nil { + if fi.Mode().IsRegular() { + // we have space if we are overwriting a bigger file with a smaller one + return initialSize >= fi.Size() + } + } } - if (checkFiles && c.User.QuotaFiles > 0 && numFile >= c.User.QuotaFiles) || - (c.User.QuotaSize > 0 && size >= c.User.QuotaSize) { - c.Log(logger.LevelDebug, logSender, "quota exceed for user %#v, num files: %v/%v, size: %v/%v check files: %v", - c.User.Username, numFile, c.User.QuotaFiles, size, c.User.QuotaSize, checkFiles) - return false + return false + } + return true +} + +func (c Connection) hasSpace(checkFiles bool, requestPath string) bool { + if dataprovider.GetQuotaTracking() == 0 { + return true + } + var quotaSize, usedSize int64 + var quotaFiles, numFiles int + var err error + var vfolder vfs.VirtualFolder + vfolder, err = c.User.GetVirtualFolderForPath(requestPath) + if err == nil && !vfolder.IsIncludedInUserQuota() { + if vfolder.HasNoQuotaRestrictions(checkFiles) { + return true } + quotaSize = vfolder.QuotaSize + quotaFiles = vfolder.QuotaFiles + numFiles, usedSize, err = dataprovider.GetUsedVirtualFolderQuota(dataProvider, vfolder.MappedPath) + } else { + if c.User.HasNoQuotaRestrictions(checkFiles) { + return true + } + quotaSize = c.User.QuotaSize + quotaFiles = c.User.QuotaFiles + numFiles, usedSize, err = dataprovider.GetUsedQuota(dataProvider, c.User.Username) + } + if err != nil { + c.Log(logger.LevelWarn, logSender, "error getting used quota for %#v request path %#v: %v", c.User.Username, requestPath, err) + return false + } + if (checkFiles && quotaFiles > 0 && numFiles >= quotaFiles) || + (quotaSize > 0 && usedSize >= quotaSize) { + c.Log(logger.LevelDebug, logSender, "quota exceed for user %#v, request path %#v, num files: %v/%v, size: %v/%v check files: %v", + c.User.Username, requestPath, numFiles, quotaFiles, usedSize, quotaSize, checkFiles) + return false } return true } @@ -612,3 +702,143 @@ func getOSOpenFlags(requestFlags sftp.FileOpenFlags) (flags int) { } return osFlags } + +func (c Connection) isCrossFoldersRequest(request *sftp.Request) bool { + sourceFolder, errSrc := c.User.GetVirtualFolderForPath(request.Filepath) + dstFolder, errDst := c.User.GetVirtualFolderForPath(request.Target) + if errSrc != nil && errDst != nil { + return false + } + if errSrc == nil && errDst == nil { + return sourceFolder.MappedPath != dstFolder.MappedPath + } + return true +} + +func (c Connection) isRenamePermitted(sourcePath string, request *sftp.Request) bool { + if c.fs.GetRelativePath(sourcePath) == "/" { + c.Log(logger.LevelWarn, logSender, "renaming root dir is not allowed") + return false + } + if c.User.IsVirtualFolder(request.Filepath) || c.User.IsVirtualFolder(request.Target) { + c.Log(logger.LevelWarn, logSender, "renaming a virtual folder is not allowed") + return false + } + if !c.User.IsFileAllowed(request.Filepath) || !c.User.IsFileAllowed(request.Target) { + if fi, err := c.fs.Lstat(sourcePath); err == nil && fi.Mode().IsRegular() { + c.Log(logger.LevelDebug, logSender, "renaming file is not allowed, source: %#v target: %#v", request.Filepath, + request.Target) + return false + } + } + if !c.User.HasPerm(dataprovider.PermRename, path.Dir(request.Target)) { + return false + } + return true +} + +func (c Connection) updateQuotaMoveBetweenVFolders(sourceFolder, dstFolder vfs.VirtualFolder, initialSize, filesSize int64, numFiles int) { + if sourceFolder.MappedPath == dstFolder.MappedPath { + // both files are inside the same virtual folder + if initialSize != -1 { + dataprovider.UpdateVirtualFolderQuota(dataProvider, dstFolder.BaseVirtualFolder, -numFiles, -initialSize, false) //nolint:errcheck + if dstFolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.User, -numFiles, -initialSize, false) //nolint:errcheck + } + } + return + } + // files are inside different virtual folders + dataprovider.UpdateVirtualFolderQuota(dataProvider, sourceFolder.BaseVirtualFolder, -numFiles, -filesSize, false) //nolint:errcheck + if sourceFolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.User, -numFiles, -filesSize, false) //nolint:errcheck + } + if initialSize == -1 { + dataprovider.UpdateVirtualFolderQuota(dataProvider, dstFolder.BaseVirtualFolder, numFiles, filesSize, false) //nolint:errcheck + if dstFolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.User, numFiles, filesSize, false) //nolint:errcheck + } + } else { + // we cannot have a directory here, initialSize != -1 only for files + dataprovider.UpdateVirtualFolderQuota(dataProvider, dstFolder.BaseVirtualFolder, 0, filesSize-initialSize, false) //nolint:errcheck + if dstFolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.User, 0, filesSize-initialSize, false) //nolint:errcheck + } + } +} + +func (c Connection) updateQuotaMoveFromVFolder(sourceFolder vfs.VirtualFolder, initialSize, filesSize int64, numFiles int) { + // move between a virtual folder and the user home dir + dataprovider.UpdateVirtualFolderQuota(dataProvider, sourceFolder.BaseVirtualFolder, -numFiles, -filesSize, false) //nolint:errcheck + if sourceFolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.User, -numFiles, -filesSize, false) //nolint:errcheck + } + if initialSize == -1 { + dataprovider.UpdateUserQuota(dataProvider, c.User, numFiles, filesSize, false) //nolint:errcheck + } else { + // we cannot have a directory here, initialSize != -1 only for files + dataprovider.UpdateUserQuota(dataProvider, c.User, 0, filesSize-initialSize, false) //nolint:errcheck + } +} + +func (c Connection) updateQuotaMoveToVFolder(dstFolder vfs.VirtualFolder, initialSize, filesSize int64, numFiles int) { + // move between the user home dir and a virtual folder + dataprovider.UpdateUserQuota(dataProvider, c.User, -numFiles, -filesSize, false) //nolint:errcheck + if initialSize == -1 { + dataprovider.UpdateVirtualFolderQuota(dataProvider, dstFolder.BaseVirtualFolder, numFiles, filesSize, false) //nolint:errcheck + if dstFolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.User, numFiles, filesSize, false) //nolint:errcheck + } + } else { + // we cannot have a directory here, initialSize != -1 only for files + dataprovider.UpdateVirtualFolderQuota(dataProvider, dstFolder.BaseVirtualFolder, 0, filesSize-initialSize, false) //nolint:errcheck + if dstFolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.User, 0, filesSize-initialSize, false) //nolint:errcheck + } + } +} + +func (c Connection) updateQuotaAfterRename(request *sftp.Request, targetPath string, initialSize int64) error { + // we don't allow to overwrite an existing directory so targetPath can be: + // - a new file, a symlink is as a new file here + // - a file overwriting an existing one + // - a new directory + // initialSize != -1 only when overwriting files + sourceFolder, errSrc := c.User.GetVirtualFolderForPath(request.Filepath) + dstFolder, errDst := c.User.GetVirtualFolderForPath(request.Target) + if errSrc != nil && errDst != nil { + // both files are contained inside the user home dir + if initialSize != -1 { + // we cannot have a directory here + dataprovider.UpdateUserQuota(dataProvider, c.User, -1, -initialSize, false) //nolint:errcheck + } + return nil + } + + filesSize := int64(0) + numFiles := 1 + if fi, err := c.fs.Stat(targetPath); err == nil { + if fi.Mode().IsDir() { + numFiles, filesSize, err = c.fs.GetDirSize(targetPath) + if err != nil { + logger.Warn(logSender, "", "failed to update quota after rename, error scanning moved folder %#v: %v", targetPath, err) + return err + } + } else { + filesSize = fi.Size() + } + } else { + c.Log(logger.LevelWarn, logSender, "failed to update quota after rename, file %#v stat error: %+v", targetPath, err) + return err + } + if errSrc == nil && errDst == nil { + c.updateQuotaMoveBetweenVFolders(sourceFolder, dstFolder, initialSize, filesSize, numFiles) + } + if errSrc == nil && errDst != nil { + c.updateQuotaMoveFromVFolder(sourceFolder, initialSize, filesSize, numFiles) + } + if errSrc != nil && errDst == nil { + c.updateQuotaMoveToVFolder(dstFolder, initialSize, filesSize, numFiles) + } + return nil +} diff --git a/sftpd/internal_test.go b/sftpd/internal_test.go index 4de3a78f..0a597e66 100644 --- a/sftpd/internal_test.go +++ b/sftpd/internal_test.go @@ -9,6 +9,7 @@ import ( "net" "os" "os/exec" + "path" "path/filepath" "runtime" "sync" @@ -99,6 +100,14 @@ func (fs MockOsFs) Stat(name string) (os.FileInfo, error) { return os.Stat(name) } +// Lstat returns a FileInfo describing the named file +func (fs MockOsFs) Lstat(name string) (os.FileInfo, error) { + if fs.statErr != nil { + return nil, fs.statErr + } + return os.Lstat(name) +} + // Remove removes the named file or (empty) directory. func (fs MockOsFs) Remove(name string, isDir bool) error { if fs.err != nil { @@ -203,6 +212,9 @@ func TestActionHTTP(t *testing.T) { } func TestPreDeleteAction(t *testing.T) { + if runtime.GOOS == osWindows { + t.Skip("this test is not available on Windows") + } actionsCopy := actions hookCmd, err := exec.LookPath("true") @@ -443,8 +455,6 @@ func TestMockFsErrors(t *testing.T) { request := sftp.NewRequest("Remove", testfile) err := ioutil.WriteFile(testfile, []byte("test"), 0666) assert.NoError(t, err) - err = c.handleSFTPRemove(testfile, request) - assert.EqualError(t, err, sftp.ErrSSHFxFailure.Error()) _, err = c.Filewrite(request) assert.EqualError(t, err, sftp.ErrSSHFxFailure.Error()) @@ -452,9 +462,14 @@ func TestMockFsErrors(t *testing.T) { flags.Write = true flags.Trunc = false flags.Append = true - _, err = c.handleSFTPUploadToExistingFile(flags, testfile, testfile, 0, false) + _, err = c.handleSFTPUploadToExistingFile(flags, testfile, testfile, 0, "/testfile") assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error()) + fs = newMockOsFs(errFake, nil, false, "123", os.TempDir()) + c.fs = fs + err = c.handleSFTPRemove(testfile, request) + assert.EqualError(t, err, sftp.ErrSSHFxFailure.Error()) + err = os.Remove(testfile) assert.NoError(t, err) } @@ -468,18 +483,18 @@ func TestUploadFiles(t *testing.T) { var flags sftp.FileOpenFlags flags.Write = true flags.Trunc = true - _, err := c.handleSFTPUploadToExistingFile(flags, "missing_path", "other_missing_path", 0, false) + _, err := c.handleSFTPUploadToExistingFile(flags, "missing_path", "other_missing_path", 0, "/missing_path") assert.Error(t, err, "upload to existing file must fail if one or both paths are invalid") uploadMode = uploadModeStandard - _, err = c.handleSFTPUploadToExistingFile(flags, "missing_path", "other_missing_path", 0, false) + _, err = c.handleSFTPUploadToExistingFile(flags, "missing_path", "other_missing_path", 0, "/missing_path") assert.Error(t, err, "upload to existing file must fail if one or both paths are invalid") missingFile := "missing/relative/file.txt" if runtime.GOOS == osWindows { missingFile = "missing\\relative\\file.txt" } - _, err = c.handleSFTPUploadToNewFile(".", missingFile, false) + _, err = c.handleSFTPUploadToNewFile(".", missingFile, "/missing") assert.Error(t, err, "upload new file in missing path must fail") c.fs = newMockOsFs(nil, nil, false, "123", os.TempDir()) @@ -488,7 +503,7 @@ func TestUploadFiles(t *testing.T) { err = f.Close() assert.NoError(t, err) - _, err = c.handleSFTPUploadToExistingFile(flags, f.Name(), f.Name(), 123, false) + _, err = c.handleSFTPUploadToExistingFile(flags, f.Name(), f.Name(), 123, f.Name()) assert.NoError(t, err) if assert.Equal(t, 1, len(activeTransfers)) { transfer := activeTransfers[0] @@ -572,7 +587,7 @@ func TestSFTPGetUsedQuota(t *testing.T) { connection := Connection{ User: u, } - assert.False(t, connection.hasSpace(false)) + assert.False(t, connection.hasSpace(false, "/")) } func TestSupportedSSHCommands(t *testing.T) { @@ -923,16 +938,20 @@ func TestGitVirtualFolders(t *testing.T) { args: []string{"/vdir"}, } cmd.connection.User.VirtualFolders = append(cmd.connection.User.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: os.TempDir(), + }, VirtualPath: "/vdir", - MappedPath: os.TempDir(), }) _, err = cmd.getSystemCommand() assert.EqualError(t, err, errUnsupportedConfig.Error()) cmd.connection.User.VirtualFolders = nil cmd.connection.User.VirtualFolders = append(cmd.connection.User.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: os.TempDir(), + }, VirtualPath: "/vdir", - MappedPath: os.TempDir(), }) cmd.args = []string{"/vdir/subdir"} _, err = cmd.getSystemCommand() @@ -987,8 +1006,10 @@ func TestRsyncOptions(t *testing.T) { "--munge-links must be added if the user has the create symlinks permission") sshCmd.connection.User.VirtualFolders = append(sshCmd.connection.User.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: os.TempDir(), + }, VirtualPath: "/vdir", - MappedPath: os.TempDir(), }) _, err = sshCmd.getSystemCommand() assert.EqualError(t, err, errUnsupportedConfig.Error()) @@ -1391,7 +1412,7 @@ func TestSCPErrorsMockFs(t *testing.T) { err = scpCommand.handleUpload(filepath.Base(testfile), 0) assert.EqualError(t, err, errFake.Error()) - err = scpCommand.handleUploadFile(testfile, testfile, 0, false, 4, false) + err = scpCommand.handleUploadFile(testfile, testfile, 0, false, 4, "/testfile") assert.NoError(t, err) err = os.Remove(testfile) assert.NoError(t, err) @@ -1810,3 +1831,43 @@ func TestCertCheckerInitErrors(t *testing.T) { err = os.Remove(testfile) assert.NoError(t, err) } + +func TestUpdateQuotaAfterRenameMissingFile(t *testing.T) { + user := dataprovider.User{ + Username: "username", + HomeDir: filepath.Join(os.TempDir(), "home"), + } + mappedPath := filepath.Join(os.TempDir(), "vdir") + user.Permissions = make(map[string][]string) + user.Permissions["/"] = []string{dataprovider.PermAny} + user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath, + }, + VirtualPath: "/vdir", + }) + c := Connection{ + fs: vfs.NewOsFs("id", os.TempDir(), nil), + User: user, + } + request := sftp.NewRequest("Rename", "/testfile") + request.Filepath = "/dir" + request.Target = path.Join("vdir", "dir") + if runtime.GOOS != "windows" { + testDirPath := filepath.Join(mappedPath, "dir") + err := os.MkdirAll(testDirPath, 0777) + assert.NoError(t, err) + err = os.Chmod(testDirPath, 0001) + assert.NoError(t, err) + err = c.updateQuotaAfterRename(request, testDirPath, 0) + assert.Error(t, err) + err = os.Chmod(testDirPath, 0777) + assert.NoError(t, err) + err = os.RemoveAll(testDirPath) + assert.NoError(t, err) + } + request.Target = "/testfile1" + request.Filepath = path.Join("vdir", "file") + err := c.updateQuotaAfterRename(request, filepath.Join(os.TempDir(), "vdir", "file"), 0) + assert.Error(t, err) +} diff --git a/sftpd/scp.go b/sftpd/scp.go index 235b1f58..83a1cece 100644 --- a/sftpd/scp.go +++ b/sftpd/scp.go @@ -187,10 +187,9 @@ func (c *scpCommand) getUploadFileData(sizeToRead int64, transfer *Transfer) err return c.sendConfirmationMessage() } -func (c *scpCommand) handleUploadFile(requestPath, filePath string, sizeToRead int64, isNewFile bool, fileSize int64, - isExcludedFromQuota bool) error { - if !c.connection.hasSpace(true) { - err := fmt.Errorf("denying file write due to space limit") +func (c *scpCommand) handleUploadFile(resolvedPath, filePath string, sizeToRead int64, isNewFile bool, fileSize int64, requestPath string) error { + if !c.connection.hasSpace(true, requestPath) { + err := fmt.Errorf("denying file write due to quota limits") c.connection.Log(logger.LevelWarn, logSenderSCP, "error uploading file: %#v, err: %v", filePath, err) c.sendErrorMessage(err) return err @@ -199,7 +198,13 @@ func (c *scpCommand) handleUploadFile(requestPath, filePath string, sizeToRead i initialSize := int64(0) if !isNewFile { if vfs.IsLocalOsFs(c.connection.fs) { - if !isExcludedFromQuota { + vfolder, err := c.connection.User.GetVirtualFolderForPath(requestPath) + if err == nil { + dataprovider.UpdateVirtualFolderQuota(dataProvider, vfolder.BaseVirtualFolder, 0, -fileSize, false) //nolint:errcheck + if vfolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, c.connection.User, 0, -fileSize, false) //nolint:errcheck + } + } else { dataprovider.UpdateUserQuota(dataProvider, c.connection.User, 0, -fileSize, false) //nolint:errcheck } } else { @@ -208,7 +213,7 @@ func (c *scpCommand) handleUploadFile(requestPath, filePath string, sizeToRead i } file, w, cancelFn, err := c.connection.fs.Create(filePath, 0) if err != nil { - c.connection.Log(logger.LevelError, logSenderSCP, "error creating file %#v: %v", requestPath, err) + c.connection.Log(logger.LevelError, logSenderSCP, "error creating file %#v: %v", resolvedPath, err) c.sendErrorMessage(err) return err } @@ -216,26 +221,26 @@ func (c *scpCommand) handleUploadFile(requestPath, filePath string, sizeToRead i vfs.SetPathPermissions(c.connection.fs, filePath, c.connection.User.GetUID(), c.connection.User.GetGID()) transfer := Transfer{ - file: file, - readerAt: nil, - writerAt: w, - cancelFn: cancelFn, - path: requestPath, - start: time.Now(), - bytesSent: 0, - bytesReceived: 0, - user: c.connection.User, - connectionID: c.connection.ID, - transferType: transferUpload, - lastActivity: time.Now(), - isNewFile: isNewFile, - protocol: c.connection.protocol, - transferError: nil, - isFinished: false, - minWriteOffset: 0, - initialSize: initialSize, - isExcludedFromQuota: isExcludedFromQuota, - lock: new(sync.Mutex), + file: file, + readerAt: nil, + writerAt: w, + cancelFn: cancelFn, + path: resolvedPath, + start: time.Now(), + bytesSent: 0, + bytesReceived: 0, + user: c.connection.User, + connectionID: c.connection.ID, + transferType: transferUpload, + lastActivity: time.Now(), + isNewFile: isNewFile, + protocol: c.connection.protocol, + transferError: nil, + isFinished: false, + minWriteOffset: 0, + initialSize: initialSize, + requestPath: requestPath, + lock: new(sync.Mutex), } addTransfer(&transfer) @@ -262,14 +267,14 @@ func (c *scpCommand) handleUpload(uploadFilePath string, sizeToRead int64) error if isAtomicUploadEnabled() && c.connection.fs.IsAtomicUploadSupported() { filePath = c.connection.fs.GetAtomicUploadPath(p) } - stat, statErr := c.connection.fs.Stat(p) - if c.connection.fs.IsNotExist(statErr) { + stat, statErr := c.connection.fs.Lstat(p) + if (statErr == nil && stat.Mode()&os.ModeSymlink == os.ModeSymlink) || c.connection.fs.IsNotExist(statErr) { if !c.connection.User.HasPerm(dataprovider.PermUpload, path.Dir(uploadFilePath)) { c.connection.Log(logger.LevelWarn, logSenderSCP, "cannot upload file: %#v, permission denied", uploadFilePath) c.sendErrorMessage(errPermission) return errPermission } - return c.handleUploadFile(p, filePath, sizeToRead, true, 0, c.connection.User.IsFileExcludedFromQuota(uploadFilePath)) + return c.handleUploadFile(p, filePath, sizeToRead, true, 0, uploadFilePath) } if statErr != nil { @@ -301,7 +306,7 @@ func (c *scpCommand) handleUpload(uploadFilePath string, sizeToRead int64) error } } - return c.handleUploadFile(p, filePath, sizeToRead, false, stat.Size(), c.connection.User.IsFileExcludedFromQuota(uploadFilePath)) + return c.handleUploadFile(p, filePath, sizeToRead, false, stat.Size(), uploadFilePath) } func (c *scpCommand) sendDownloadProtocolMessages(dirPath string, stat os.FileInfo) error { @@ -336,8 +341,8 @@ func (c *scpCommand) sendDownloadProtocolMessages(dirPath string, stat os.FileIn return err } -// we send first all the files in the root directory and then the directories -// for each directory we recursively call this method again +// We send first all the files in the root directory and then the directories. +// For each directory we recursively call this method again func (c *scpCommand) handleRecursiveDownload(dirPath string, stat os.FileInfo) error { var err error if c.isRecursive() { diff --git a/sftpd/sftpd.go b/sftpd/sftpd.go index 01e05eda..328d3182 100644 --- a/sftpd/sftpd.go +++ b/sftpd/sftpd.go @@ -59,16 +59,17 @@ const ( ) var ( - mutex sync.RWMutex - openConnections map[string]Connection - activeTransfers []*Transfer - idleTimeout time.Duration - activeQuotaScans []ActiveQuotaScan - dataProvider dataprovider.Provider - actions Actions - uploadMode int - setstatMode int - supportedSSHCommands = []string{"scp", "md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum", "cd", "pwd", + mutex sync.RWMutex + openConnections map[string]Connection + activeTransfers []*Transfer + idleTimeout time.Duration + activeQuotaScans []ActiveQuotaScan + activeVFoldersQuotaScan []ActiveVirtualFolderQuotaScan + dataProvider dataprovider.Provider + actions Actions + uploadMode int + setstatMode int + supportedSSHCommands = []string{"scp", "md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum", "cd", "pwd", "git-receive-pack", "git-upload-pack", "git-upload-archive", "rsync"} defaultSSHCommands = []string{"md5sum", "sha1sum", "cd", "pwd", "scp"} sshHashCommands = []string{"md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum"} @@ -86,7 +87,7 @@ type connectionTransfer struct { Path string `json:"path"` } -// ActiveQuotaScan defines an active quota scan +// ActiveQuotaScan defines an active quota scan for a user home dir type ActiveQuotaScan struct { // Username to which the quota scan refers Username string `json:"username"` @@ -94,6 +95,14 @@ type ActiveQuotaScan struct { StartTime int64 `json:"start_time"` } +// ActiveVirtualFolderQuotaScan defines an active quota scan for a virtual folder +type ActiveVirtualFolderQuotaScan struct { + // folder path to which the quota scan refers + MappedPath string `json:"mapped_path"` + // quota scan start time as unix timestamp in milliseconds + StartTime int64 `json:"start_time"` +} + // Actions to execute on SFTP create, download, delete and rename. // An external command can be executed and/or an HTTP notification can be fired type Actions struct { @@ -278,7 +287,7 @@ func getActiveSessions(username string) int { return numSessions } -// GetQuotaScans returns the active quota scans +// GetQuotaScans returns the active quota scans for users home directories func GetQuotaScans() []ActiveQuotaScan { mutex.RLock() defer mutex.RUnlock() @@ -320,8 +329,56 @@ func RemoveQuotaScan(username string) error { activeQuotaScans[indexToRemove] = activeQuotaScans[len(activeQuotaScans)-1] activeQuotaScans = activeQuotaScans[:len(activeQuotaScans)-1] } else { - logger.Warn(logSender, "", "quota scan to remove not found for user: %v", username) - err = fmt.Errorf("quota scan to remove not found for user: %v", username) + err = fmt.Errorf("quota scan to remove not found for user: %#v", username) + logger.Warn(logSender, "", "error: %v", err) + } + return err +} + +// GetVFoldersQuotaScans returns the active quota scans for virtual folders +func GetVFoldersQuotaScans() []ActiveVirtualFolderQuotaScan { + mutex.RLock() + defer mutex.RUnlock() + scans := make([]ActiveVirtualFolderQuotaScan, len(activeVFoldersQuotaScan)) + copy(scans, activeVFoldersQuotaScan) + return scans +} + +// AddVFolderQuotaScan add a virtual folder to the ones with active quota scans. +// Returns false if the folder has a quota scan already running +func AddVFolderQuotaScan(folderPath string) bool { + mutex.Lock() + defer mutex.Unlock() + for _, s := range activeVFoldersQuotaScan { + if s.MappedPath == folderPath { + return false + } + } + activeVFoldersQuotaScan = append(activeVFoldersQuotaScan, ActiveVirtualFolderQuotaScan{ + MappedPath: folderPath, + StartTime: utils.GetTimeAsMsSinceEpoch(time.Now()), + }) + return true +} + +// RemoveVFolderQuotaScan removes a folder from the ones with active quota scans +func RemoveVFolderQuotaScan(folderPath string) error { + mutex.Lock() + defer mutex.Unlock() + var err error + indexToRemove := -1 + for i, s := range activeVFoldersQuotaScan { + if s.MappedPath == folderPath { + indexToRemove = i + break + } + } + if indexToRemove >= 0 { + activeVFoldersQuotaScan[indexToRemove] = activeVFoldersQuotaScan[len(activeVFoldersQuotaScan)-1] + activeVFoldersQuotaScan = activeVFoldersQuotaScan[:len(activeVFoldersQuotaScan)-1] + } else { + err = fmt.Errorf("quota scan to remove not found for user: %#v", folderPath) + logger.Warn(logSender, "", "error: %v", err) } return err } diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 0c55fff8..adaac8dd 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -21,6 +21,7 @@ import ( "path" "path/filepath" "runtime" + "strconv" "strings" "testing" "time" @@ -422,6 +423,9 @@ func TestDirCommands(t *testing.T) { assert.NoError(t, err) err = client.Rename("test1", "test") assert.NoError(t, err) + // rename a missing file + err = client.Rename("test1", "test2") + assert.Error(t, err) _, err = client.Lstat("/test1") assert.Error(t, err, "stat for renamed dir must not succeed") err = client.PosixRename("test", "test1") @@ -1412,6 +1416,7 @@ func TestLoginExternalAuth(t *testing.T) { if runtime.GOOS == osWindows { t.Skip("this test is not available on Windows") } + mappedPath := filepath.Join(os.TempDir(), "vdir1") extAuthScopes := []int{1, 2} for _, authScope := range extAuthScopes { var usePubKey bool @@ -1421,6 +1426,14 @@ func TestLoginExternalAuth(t *testing.T) { usePubKey = true } u := getTestUser(usePubKey) + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath, + }, + VirtualPath: "/vpath", + QuotaFiles: 1 + authScope, + QuotaSize: 10 + int64(authScope), + }) dataProvider := dataprovider.GetProvider() err := dataprovider.Close(dataProvider) assert.NoError(t, err) @@ -1454,14 +1467,22 @@ func TestLoginExternalAuth(t *testing.T) { } users, _, err := httpd.GetUsers(0, 0, defaultUsername, http.StatusOK) assert.NoError(t, err) - assert.Equal(t, 1, len(users)) + if assert.Len(t, users, 1) { + user := users[0] + if assert.Len(t, user.VirtualFolders, 1) { + folder := user.VirtualFolders[0] + assert.Equal(t, mappedPath, folder.MappedPath) + assert.Equal(t, 1+authScope, folder.QuotaFiles) + assert.Equal(t, 10+int64(authScope), folder.QuotaSize) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) + } - user := users[0] - _, err = httpd.RemoveUser(user, http.StatusOK) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK) assert.NoError(t, err) - err = os.RemoveAll(user.GetHomeDir()) - assert.NoError(t, err) - dataProvider = dataprovider.GetProvider() err = dataprovider.Close(dataProvider) assert.NoError(t, err) @@ -1603,7 +1624,7 @@ func TestQuotaDisabledError(t *testing.T) { sftpd.SetDataProvider(dataprovider.GetProvider()) usePubKey := false u := getTestUser(usePubKey) - u.QuotaFiles = 10 + u.QuotaFiles = 1 user, _, err := httpd.AddUser(u, http.StatusOK) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) @@ -1616,6 +1637,10 @@ func TestQuotaDisabledError(t *testing.T) { assert.NoError(t, err) err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName+"1", testFileSize, client) + assert.NoError(t, err) + err = client.Rename(testFileName+"1", testFileName+".rename") + assert.NoError(t, err) err = os.Remove(testFilePath) assert.NoError(t, err) } @@ -1678,8 +1703,6 @@ func TestQuotaFileReplace(t *testing.T) { assert.NoError(t, err) err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) assert.NoError(t, err) - user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) - assert.NoError(t, err) // now replace the same file, the quota must not change err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) assert.NoError(t, err) @@ -1687,9 +1710,25 @@ func TestQuotaFileReplace(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles) assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize) + // now create a symlink, replace it with a file and check the quota + // replacing a symlink is like uploading a new file + err = client.Symlink(testFileName, testFileName+".link") + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles) + assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize) + expectedQuotaFiles = expectedQuotaFiles + 1 + expectedQuotaSize = expectedQuotaSize + testFileSize + err = sftpUploadFile(testFilePath, testFileName+".link", testFileSize, client) + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles) + assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize) } // now set a quota size restriction and upload the same file, upload should fail for space limit exceeded - user.QuotaSize = testFileSize - 1 + user.QuotaSize = testFileSize*2 - 1 user, _, err = httpd.UpdateUser(user, http.StatusOK) assert.NoError(t, err) client, err = getSftpClient(user, usePubKey) @@ -1707,6 +1746,90 @@ func TestQuotaFileReplace(t *testing.T) { assert.NoError(t, err) } +func TestQuotaRename(t *testing.T) { + usePubKey := false + u := getTestUser(usePubKey) + u.QuotaFiles = 1000 + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) + testFileSize := int64(65535) + testFileSize1 := int64(65537) + testFileName := "test_file.dat" + testFileName1 := "test_file1.dat" //nolint:goconst + testFilePath := filepath.Join(homeBasePath, testFileName) + testFilePath1 := filepath.Join(homeBasePath, testFileName1) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = createTestFile(testFilePath1, testFileSize1) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = client.Rename(testFileName, testFileName+".rename") + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, testFileName1, testFileSize1, client) + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + err = client.Rename(testFileName1, testFileName+".rename") + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize1, user.UsedQuotaSize) + err = client.Symlink(testFileName+".rename", testFileName+".symlink") + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + // overwrite a symlink + err = client.Rename(testFileName, testFileName+".symlink") + assert.NoError(t, err) + err = client.Mkdir("testdir") + assert.NoError(t, err) + err = client.Rename("testdir", "testdir1") + assert.NoError(t, err) + err = client.Mkdir("testdir") + assert.NoError(t, err) + err = client.Rename("testdir", "testdir1") + assert.Error(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + testDir := "tdir" + err = client.Mkdir(testDir) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(testDir, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, path.Join(testDir, testFileName1), testFileSize1, client) + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 4, user.UsedQuotaFiles) + assert.Equal(t, testFileSize*2+testFileSize1*2, user.UsedQuotaSize) + err = client.Rename(testDir, testDir+"1") + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 4, user.UsedQuotaFiles) + assert.Equal(t, testFileSize*2+testFileSize1*2, user.UsedQuotaSize) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + err = os.Remove(testFilePath) + assert.NoError(t, err) + err = os.Remove(testFilePath1) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) +} + func TestQuotaScan(t *testing.T) { usePubKey := false user, _, err := httpd.AddUser(getTestUser(usePubKey), http.StatusOK) @@ -1733,7 +1856,7 @@ func TestQuotaScan(t *testing.T) { assert.NoError(t, err) _, err = httpd.StartQuotaScan(user, http.StatusCreated) assert.NoError(t, err) - err = waitQuotaScans() + err = waitQuotaScans(1) assert.NoError(t, err) user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) assert.NoError(t, err) @@ -1754,32 +1877,48 @@ func TestMultipleQuotaScans(t *testing.T) { assert.NoError(t, err) activeScans := sftpd.GetQuotaScans() assert.Equal(t, 0, len(activeScans)) + err = sftpd.RemoveQuotaScan(defaultUsername) + assert.Error(t, err) } -func TestQuotaSize(t *testing.T) { +func TestQuotaLimits(t *testing.T) { usePubKey := false - testFileSize := int64(65535) u := getTestUser(usePubKey) u.QuotaFiles = 1 - u.QuotaSize = testFileSize - 1 user, _, err := httpd.AddUser(u, http.StatusOK) assert.NoError(t, err) + testFileSize := int64(65535) + testFileName := "test_file.dat" + testFilePath := filepath.Join(homeBasePath, testFileName) + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + // test quota files client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() - testFileName := "test_file.dat" - testFilePath := filepath.Join(homeBasePath, testFileName) - err = createTestFile(testFilePath, testFileSize) - assert.NoError(t, err) err = sftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client) assert.NoError(t, err) err = sftpUploadFile(testFilePath, testFileName+".quota.1", testFileSize, client) - assert.Error(t, err, "user is over quota file upload must fail") - err = client.Remove(testFileName + ".quota") - assert.NoError(t, err) - err = os.Remove(testFilePath) + assert.Error(t, err, "user is over quota files, upload must fail") + // rename should work + err = client.Rename(testFileName+".quota", testFileName) assert.NoError(t, err) } + // test quota size + user.QuotaSize = testFileSize - 1 + user.QuotaFiles = 0 + user, _, err = httpd.UpdateUser(user, http.StatusOK) + assert.NoError(t, err) + client, err = getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + err = sftpUploadFile(testFilePath, testFileName+".quota.1", testFileSize, client) + assert.Error(t, err, "user is over quota size, upload must fail") + err = client.Rename(testFileName, testFileName+".quota") + assert.NoError(t, err) + } + err = os.Remove(testFilePath) + assert.NoError(t, err) _, err = httpd.RemoveUser(user, http.StatusOK) assert.NoError(t, err) err = os.RemoveAll(user.GetHomeDir()) @@ -1902,10 +2041,12 @@ func TestVirtualFolders(t *testing.T) { usePubKey := true u := getTestUser(usePubKey) mappedPath := filepath.Join(os.TempDir(), "vdir") - vdirPath := "/vdir" //nolint:goconst + vdirPath := "/vdir/subdir" u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath, + }, VirtualPath: vdirPath, - MappedPath: mappedPath, }) err := os.MkdirAll(mappedPath, 0777) assert.NoError(t, err) @@ -1932,6 +2073,14 @@ func TestVirtualFolders(t *testing.T) { assert.Error(t, err, "creating a virtual folder must fail") err = client.Symlink(path.Join(vdirPath, testFileName), vdirPath) assert.Error(t, err, "symlink to a virtual folder must fail") + err = client.Rename("/vdir", "/vdir1") + assert.Error(t, err, "renaming a directory with a virtual folder inside must fail") + err = client.RemoveDirectory("/vdir") + assert.Error(t, err, "removing a directory with a virtual folder inside must fail") + err = client.Mkdir("vdir1") + assert.NoError(t, err) + err = client.Rename("vdir1", "vdir2") + assert.NoError(t, err) err = os.Remove(testFilePath) assert.NoError(t, err) err = os.Remove(localDownloadPath) @@ -1939,28 +2088,236 @@ func TestVirtualFolders(t *testing.T) { } _, err = httpd.RemoveUser(user, http.StatusOK) assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK) + assert.NoError(t, err) err = os.RemoveAll(user.GetHomeDir()) assert.NoError(t, err) err = os.RemoveAll(mappedPath) assert.NoError(t, err) } -func TestVirtualFoldersQuota(t *testing.T) { +func TestVirtualFoldersQuotaLimit(t *testing.T) { usePubKey := false + u1 := getTestUser(usePubKey) + u1.QuotaFiles = 1 + mappedPath1 := filepath.Join(os.TempDir(), "vdir1") + vdirPath1 := "/vdir1" //nolint:goconst + mappedPath2 := filepath.Join(os.TempDir(), "vdir2") + vdirPath2 := "/vdir2" //nolint:goconst + u1.VirtualFolders = append(u1.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath1, + QuotaFiles: -1, + QuotaSize: -1, + }) + u1.VirtualFolders = append(u1.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + QuotaFiles: 1, + QuotaSize: 0, + }) + testFileSize := int64(131072) + testFileName := "test_file.dat" + testFilePath := filepath.Join(homeBasePath, testFileName) + err := createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + u2 := getTestUser(usePubKey) + u2.QuotaSize = testFileSize - 1 + u2.VirtualFolders = append(u2.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath1, + QuotaFiles: -1, + QuotaSize: -1, + }) + u2.VirtualFolders = append(u2.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + QuotaFiles: 0, + QuotaSize: testFileSize - 1, + }) + users := []dataprovider.User{u1, u2} + for _, u := range users { + err = os.MkdirAll(mappedPath1, 0777) + assert.NoError(t, err) + err = os.MkdirAll(mappedPath2, 0777) + assert.NoError(t, err) + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.Error(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName+"1"), testFileSize, client) + assert.Error(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName+"1"), testFileSize, client) + assert.Error(t, err) + err = client.Remove(path.Join(vdirPath1, testFileName)) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client) + assert.Error(t, err) + // now test renames + err = client.Rename(testFileName, path.Join(vdirPath1, testFileName)) + assert.NoError(t, err) + err = client.Rename(path.Join(vdirPath1, testFileName), path.Join(vdirPath1, testFileName+".rename")) + assert.NoError(t, err) + err = client.Rename(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testFileName+".rename")) + assert.NoError(t, err) + err = client.Rename(path.Join(vdirPath2, testFileName+".rename"), testFileName+".rename") + assert.Error(t, err) + err = client.Rename(path.Join(vdirPath2, testFileName+".rename"), path.Join(vdirPath1, testFileName)) + assert.Error(t, err) + err = client.Rename(path.Join(vdirPath1, testFileName+".rename"), path.Join(vdirPath2, testFileName)) + assert.Error(t, err) + err = client.Rename(path.Join(vdirPath1, testFileName+".rename"), testFileName) + assert.Error(t, err) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, 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.Remove(testFilePath) + assert.NoError(t, err) +} + +func TestVirtualFoldersQuotaRenameOverwrite(t *testing.T) { + usePubKey := true + testFileSize := int64(131072) + testFileName := "test_file.dat" + testFilePath := filepath.Join(homeBasePath, testFileName) + testFileSize1 := int64(65537) + testFileName1 := "test_file1.dat" + testFilePath1 := filepath.Join(homeBasePath, testFileName1) + err := createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = createTestFile(testFilePath1, testFileSize1) + assert.NoError(t, err) u := getTestUser(usePubKey) - u.QuotaFiles = 100 + u.QuotaFiles = 0 + u.QuotaSize = 0 mappedPath1 := filepath.Join(os.TempDir(), "vdir1") vdirPath1 := "/vdir1" mappedPath2 := filepath.Join(os.TempDir(), "vdir2") vdirPath2 := "/vdir2" u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, VirtualPath: vdirPath1, - MappedPath: mappedPath1, + QuotaFiles: 2, + QuotaSize: 0, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ - VirtualPath: vdirPath2, - MappedPath: mappedPath2, - ExcludeFromQuota: true, + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + QuotaFiles: 0, + QuotaSize: testFileSize + testFileSize1 - 1, + }) + err = os.MkdirAll(mappedPath1, 0777) + assert.NoError(t, err) + err = os.MkdirAll(mappedPath2, 0777) + assert.NoError(t, err) + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, testFileName1), testFileSize1, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, path.Join(vdirPath2, testFileName1), testFileSize1, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, testFileName1, testFileSize1, 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 = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, 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) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, 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.Remove(testFilePath) + assert.NoError(t, err) + err = os.Remove(testFilePath1) + assert.NoError(t, err) +} + +func TestVirtualFoldersQuotaValues(t *testing.T) { + usePubKey := false + u := getTestUser(usePubKey) + u.QuotaFiles = 100 + mappedPath1 := filepath.Join(os.TempDir(), "vdir1") + vdirPath1 := "/vdir1" //nolint:goconst + mappedPath2 := filepath.Join(os.TempDir(), "vdir2") + vdirPath2 := "/vdir2" //nolint:goconst + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath1, + // quota is included in the user's one + QuotaFiles: -1, + QuotaSize: -1, + }) + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + // quota is unlimited and excluded from user's one + QuotaFiles: 0, + QuotaSize: 0, }) err := os.MkdirAll(mappedPath1, 0777) assert.NoError(t, err) @@ -1978,6 +2335,9 @@ func TestVirtualFoldersQuota(t *testing.T) { assert.NoError(t, err) err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) assert.NoError(t, err) + // we copy the same file two times to test quota update on file overwrite + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client) + assert.NoError(t, err) err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client) assert.NoError(t, err) err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client) @@ -1990,15 +2350,1288 @@ func TestVirtualFoldersQuota(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles) assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize) + folder, _, err := httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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) + + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, int64(0), f.UsedQuotaSize) + assert.Equal(t, 0, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, int64(0), f.UsedQuotaSize) + assert.Equal(t, 0, f.UsedQuotaFiles) + } + err = os.Remove(testFilePath) + assert.NoError(t, err) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, 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) { + usePubKey := false + u := getTestUser(usePubKey) + u.QuotaFiles = 100 + mappedPath1 := filepath.Join(os.TempDir(), "vdir1") + vdirPath1 := "/vdir1" + mappedPath2 := filepath.Join(os.TempDir(), "vdir2") + vdirPath2 := "/vdir2" + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath1, + // quota is included in the user's one + QuotaFiles: -1, + QuotaSize: -1, + }) + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + // quota is unlimited and excluded from user's one + QuotaFiles: 0, + QuotaSize: 0, + }) + err := os.MkdirAll(mappedPath1, 0777) + assert.NoError(t, err) + err = os.MkdirAll(mappedPath2, 0777) + assert.NoError(t, err) + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + testFileName := "test_file.dat" + testFileName1 := "test_file1.dat" + testFileSize := int64(131072) + testFileSize1 := int64(65535) + testFilePath := filepath.Join(homeBasePath, testFileName) + testFilePath1 := filepath.Join(homeBasePath, testFileName1) + dir1 := "dir1" //nolint:goconst + dir2 := "dir2" //nolint:goconst + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = createTestFile(testFilePath1, testFileSize1) + 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 = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client) + assert.NoError(t, err) user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) assert.NoError(t, err) - assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles) - assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + folder, _, err := httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 2, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 2, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + + err = os.Remove(testFilePath) + assert.NoError(t, err) + err = os.Remove(testFilePath1) + assert.NoError(t, err) } _, err = httpd.RemoveUser(user, http.StatusOK) assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, 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) { + usePubKey := true + u := getTestUser(usePubKey) + u.QuotaFiles = 100 + mappedPath1 := filepath.Join(os.TempDir(), "vdir1") + vdirPath1 := "/vdir1" + mappedPath2 := filepath.Join(os.TempDir(), "vdir2") + vdirPath2 := "/vdir2" + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath1, + // quota is included in the user's one + QuotaFiles: -1, + QuotaSize: -1, + }) + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + // quota is unlimited and excluded from user's one + QuotaFiles: 0, + QuotaSize: 0, + }) + err := os.MkdirAll(mappedPath1, 0777) + assert.NoError(t, err) + err = os.MkdirAll(mappedPath2, 0777) + assert.NoError(t, err) + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + testFileName := "test_file.dat" + testFileName1 := "test_file1.dat" + testFileSize := int64(131072) + testFileSize1 := int64(65535) + testFilePath := filepath.Join(homeBasePath, testFileName) + testFilePath1 := filepath.Join(homeBasePath, testFileName1) + dir1 := "dir1" + dir2 := "dir2" + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = createTestFile(testFilePath1, testFileSize1) + 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 = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize, user.UsedQuotaSize) + folder, _, err := httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize*2, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize*2, f.UsedQuotaSize) + assert.Equal(t, 2, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir2, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, path.Join(vdirPath2, dir2, testFileName), testFileSize1, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 5, user.UsedQuotaFiles) + assert.Equal(t, testFileSize1*3+testFileSize*2, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1*3+testFileSize*2, f.UsedQuotaSize) + assert.Equal(t, 5, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 3, user.UsedQuotaFiles) + assert.Equal(t, testFileSize1*2+testFileSize, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1*2+testFileSize, f.UsedQuotaSize) + assert.Equal(t, 3, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 2, f.UsedQuotaFiles) + } + + err = os.Remove(testFilePath) + assert.NoError(t, err) + err = os.Remove(testFilePath1) + assert.NoError(t, err) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, 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) { + usePubKey := false + u := getTestUser(usePubKey) + u.QuotaFiles = 100 + mappedPath1 := filepath.Join(os.TempDir(), "vdir1") + vdirPath1 := "/vdir1" + mappedPath2 := filepath.Join(os.TempDir(), "vdir2") + vdirPath2 := "/vdir2" + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath1, + // quota is included in the user's one + QuotaFiles: -1, + QuotaSize: -1, + }) + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + // quota is unlimited and excluded from user's one + QuotaFiles: 0, + QuotaSize: 0, + }) + err := os.MkdirAll(mappedPath1, 0777) + assert.NoError(t, err) + err = os.MkdirAll(mappedPath2, 0777) + assert.NoError(t, err) + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + testFileName := "test_file.dat" + testFileName1 := "test_file1.dat" + testFileSize := int64(131072) + testFileSize1 := int64(65535) + testFilePath := filepath.Join(homeBasePath, testFileName) + testFilePath1 := filepath.Join(homeBasePath, testFileName1) + dir1 := "dir1" + dir2 := "dir2" + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = createTestFile(testFilePath1, testFileSize1) + 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 = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + folder, _, err := httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 3, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize1+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, int64(0), f.UsedQuotaSize) + assert.Equal(t, 0, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, int64(0), f.UsedQuotaSize) + assert.Equal(t, 0, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, int64(0), f.UsedQuotaSize) + assert.Equal(t, 0, f.UsedQuotaFiles) + } + // dir rename + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, dir1, testFileName1), testFileSize1, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 6, user.UsedQuotaFiles) + assert.Equal(t, testFileSize*3+testFileSize1*3, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 2, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 6, user.UsedQuotaFiles) + assert.Equal(t, testFileSize*3+testFileSize1*3, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, int64(0), f.UsedQuotaSize) + assert.Equal(t, 0, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, int64(0), f.UsedQuotaSize) + assert.Equal(t, 0, f.UsedQuotaFiles) + } + + err = os.Remove(testFilePath) + assert.NoError(t, err) + err = os.Remove(testFilePath1) + assert.NoError(t, err) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, 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) { + usePubKey := true + u := getTestUser(usePubKey) + u.QuotaFiles = 100 + mappedPath1 := filepath.Join(os.TempDir(), "vdir1") + vdirPath1 := "/vdir1" + mappedPath2 := filepath.Join(os.TempDir(), "vdir2") + vdirPath2 := "/vdir2" + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath1, + // quota is included in the user's one + QuotaFiles: -1, + QuotaSize: -1, + }) + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + // quota is unlimited and excluded from user's one + QuotaFiles: 0, + QuotaSize: 0, + }) + err := os.MkdirAll(mappedPath1, 0777) + assert.NoError(t, err) + err = os.MkdirAll(mappedPath2, 0777) + assert.NoError(t, err) + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + testFileName := "test_file.dat" + testFileName1 := "test_file1.dat" + testFileSize := int64(131072) + testFileSize1 := int64(65535) + testFilePath := filepath.Join(homeBasePath, testFileName) + testFilePath1 := filepath.Join(homeBasePath, testFileName1) + dir1 := "dir1" + dir2 := "dir2" + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = createTestFile(testFilePath1, testFileSize1) + 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 = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + folder, _, err := httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, testFileName1, testFileSize1, client) + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 2, user.UsedQuotaFiles) + assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + + err = client.Mkdir(dir1) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 3, user.UsedQuotaFiles) + assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 3, user.UsedQuotaFiles) + assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize*2+testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 3, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + err = client.Mkdir(dir1) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(dir1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath1, 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 = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 3, user.UsedQuotaFiles) + assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize) + folder, _, err = httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize*2+testFileSize1, f.UsedQuotaSize) + assert.Equal(t, 3, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize1*2+testFileSize, f.UsedQuotaSize) + assert.Equal(t, 3, f.UsedQuotaFiles) + } + + err = os.Remove(testFilePath) + assert.NoError(t, err) + err = os.Remove(testFilePath1) + assert.NoError(t, err) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, 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 TestVirtualFoldersLink(t *testing.T) { + usePubKey := true + u := getTestUser(usePubKey) + mappedPath1 := filepath.Join(os.TempDir(), "vdir1") + vdirPath1 := "/vdir1" + mappedPath2 := filepath.Join(os.TempDir(), "vdir2") + vdirPath2 := "/vdir2" + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath1, + // quota is included in the user's one + QuotaFiles: -1, + QuotaSize: -1, + }) + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + // quota is unlimited and excluded from user's one + QuotaFiles: 0, + QuotaSize: 0, + }) + err := os.MkdirAll(mappedPath1, 0777) + assert.NoError(t, err) + err = os.MkdirAll(mappedPath2, 0777) + assert.NoError(t, err) + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + testFileName := "test_file.dat" + testFileSize := int64(131072) + testFilePath := filepath.Join(homeBasePath, testFileName) + testDir := "adir" + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, 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(testFileName, path.Join(vdirPath1, testFileName+".link1")) + assert.Error(t, err) + err = client.Symlink(testFileName, path.Join(vdirPath1, testDir, testFileName+".link1")) + assert.Error(t, err) + err = client.Symlink(testFileName, path.Join(vdirPath2, testFileName+".link1")) + assert.Error(t, err) + err = client.Symlink(testFileName, path.Join(vdirPath2, testDir, testFileName+".link1")) + assert.Error(t, err) + err = client.Symlink(path.Join(vdirPath1, testFileName), testFileName+".link1") + assert.Error(t, err) + err = client.Symlink(path.Join(vdirPath2, testFileName), testFileName+".link1") + assert.Error(t, err) + err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath2, testDir, testFileName+".link1")) + assert.Error(t, err) + err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath1, testFileName+".link1")) + assert.Error(t, err) + err = os.Remove(testFilePath) + assert.NoError(t, err) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, 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 TestVirtualFolderQuotaScan(t *testing.T) { + mappedPath := filepath.Join(os.TempDir(), "mapped_dir") + err := os.MkdirAll(mappedPath, 0777) + assert.NoError(t, err) + testFileSize := int64(65535) + testFileName := "test_file.dat" + testFilePath := filepath.Join(mappedPath, testFileName) + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + expectedQuotaSize := testFileSize + expectedQuotaFiles := 1 + folder, _, err := httpd.AddFolder(vfs.BaseVirtualFolder{ + MappedPath: mappedPath, + }, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.StartFolderQuotaScan(folder, http.StatusCreated) + assert.NoError(t, err) + err = waitQuotaScans(1) + assert.NoError(t, err) + folders, _, err := httpd.GetFolders(0, 0, mappedPath, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folders, 1) { + folder = folders[0] + assert.Equal(t, expectedQuotaFiles, folder.UsedQuotaFiles) + assert.Equal(t, expectedQuotaSize, folder.UsedQuotaSize) + } + _, err = httpd.RemoveFolder(folder, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(mappedPath) + assert.NoError(t, err) +} + +func TestVFolderMultipleQuotaScan(t *testing.T) { + folderPath := filepath.Join(os.TempDir(), "folder_path") + res := sftpd.AddVFolderQuotaScan(folderPath) + assert.True(t, res) + res = sftpd.AddVFolderQuotaScan(folderPath) + assert.False(t, res) + err := sftpd.RemoveVFolderQuotaScan(folderPath) + assert.NoError(t, err) + activeScans := sftpd.GetVFoldersQuotaScans() + assert.Len(t, activeScans, 0) + err = sftpd.RemoveVFolderQuotaScan(folderPath) + assert.Error(t, err) +} + +func TestVFolderQuotaSize(t *testing.T) { + usePubKey := false + u := getTestUser(usePubKey) + testFileSize := int64(131072) + u.QuotaFiles = 1 + u.QuotaSize = testFileSize - 1 + mappedPath1 := filepath.Join(os.TempDir(), "vdir1") + vdirPath1 := "/vpath1" + mappedPath2 := filepath.Join(os.TempDir(), "vdir2") + vdirPath2 := "/vpath2" + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath1, + // quota is included in the user's one + QuotaFiles: -1, + QuotaSize: -1, + }) + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + QuotaFiles: 1, + QuotaSize: testFileSize * 2, + }) + err := os.MkdirAll(mappedPath1, 0777) + assert.NoError(t, err) + err = os.MkdirAll(mappedPath2, 0777) + assert.NoError(t, err) + testFileName := "test_file.dat" + testFilePath := filepath.Join(homeBasePath, testFileName) + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + // vdir1 is included in the user quota so upload must fail + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client) + assert.Error(t, err) + // upload to vdir2 must work, it has its own quota + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client) + assert.NoError(t, err) + // now vdir2 is over quota + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName+".quota"), testFileSize, client) + assert.Error(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize, user.UsedQuotaSize) + // remove a file + err = client.Remove(testFileName) + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 0, user.UsedQuotaFiles) + assert.Equal(t, int64(0), user.UsedQuotaSize) + // upload to vdir1 must work now + err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client) + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 1, user.UsedQuotaFiles) + assert.Equal(t, testFileSize, user.UsedQuotaSize) + + folder, _, err := httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, testFileSize, f.UsedQuotaSize) + assert.Equal(t, 1, f.UsedQuotaFiles) + } + } + // now create another user with the same shared folder but a different quota limit + u.Username = defaultUsername + "1" + u.VirtualFolders = nil + u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + QuotaFiles: 10, + QuotaSize: testFileSize*2 - 1, + }) + user1, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err = getSftpClient(user1, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName+".quota"), testFileSize, client) + assert.NoError(t, err) + // the folder is now over quota for size but not for files + err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName+".quota1"), testFileSize, client) + assert.Error(t, err) + } + + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveUser(user1, http.StatusOK) + assert.NoError(t, err) + + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, http.StatusOK) + assert.NoError(t, err) err = os.RemoveAll(user.GetHomeDir()) assert.NoError(t, err) err = os.RemoveAll(mappedPath1) @@ -2352,7 +3985,47 @@ func TestPermRename(t *testing.T) { err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) assert.NoError(t, err) err = client.Rename(testFileName, testFileName+".rename") - assert.Error(t, err, "rename without permission should not succeed") + if assert.Error(t, err) { + assert.Contains(t, err.Error(), permissionErrorString) + } + err = client.Remove(testFileName) + assert.NoError(t, err) + err = os.Remove(testFilePath) + assert.NoError(t, err) + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) +} + +//nolint:dupl +func TestPermRenameOverwrite(t *testing.T) { + usePubKey := false + u := getTestUser(usePubKey) + u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete, + dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermChmod, dataprovider.PermRename, + dataprovider.PermChown, dataprovider.PermChtimes} + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + testFileName := "test_file.dat" + testFilePath := filepath.Join(homeBasePath, testFileName) + testFileSize := int64(65535) + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = client.Rename(testFileName, testFileName+".rename") + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = client.Rename(testFileName, testFileName+".rename") + if assert.Error(t, err) { + assert.Contains(t, err.Error(), permissionErrorString) + } err = client.Remove(testFileName) assert.NoError(t, err) err = os.Remove(testFilePath) @@ -2515,7 +4188,7 @@ func TestSubDirsUploads(t *testing.T) { usePubKey := true u := getTestUser(usePubKey) u.Permissions["/"] = []string{dataprovider.PermAny} - u.Permissions["/subdir"] = []string{dataprovider.PermChtimes, dataprovider.PermDownload} + u.Permissions["/subdir"] = []string{dataprovider.PermChtimes, dataprovider.PermDownload, dataprovider.PermOverwrite} user, _, err := httpd.AddUser(u, http.StatusOK) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) @@ -2525,6 +4198,7 @@ func TestSubDirsUploads(t *testing.T) { assert.NoError(t, err) testFileName := "test_file.dat" testFileNameSub := "/subdir/test_file_dat" + testDir := "testdir" testFilePath := filepath.Join(homeBasePath, testFileName) testFileSize := int64(65535) err = createTestFile(testFilePath, testFileSize) @@ -2547,6 +4221,22 @@ func TestSubDirsUploads(t *testing.T) { } err = client.Rename(testFileName, testFileName+".rename") assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + // rename overwriting an existing file + err = client.Rename(testFileName, testFileName+".rename") + assert.NoError(t, err) + // now try to overwrite a directory + err = client.Mkdir(testDir) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = client.Rename(testFileName, testDir) + assert.Error(t, err) + err = client.Remove(testFileName) + assert.NoError(t, err) + err = client.Remove(testDir) + assert.NoError(t, err) err = client.Remove(testFileNameSub) if assert.Error(t, err) { assert.Contains(t, err.Error(), permissionErrorString) @@ -2878,10 +4568,12 @@ func TestResolvePaths(t *testing.T) { func TestVirtualRelativePaths(t *testing.T) { user := getTestUser(true) mappedPath := filepath.Join(os.TempDir(), "vdir") - vdirPath := "/vdir" + vdirPath := "/vdir" //nolint:goconst user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath, + }, VirtualPath: vdirPath, - MappedPath: mappedPath, }) err := os.MkdirAll(mappedPath, 0777) assert.NoError(t, err) @@ -2904,8 +4596,10 @@ func TestResolveVirtualPaths(t *testing.T) { mappedPath := filepath.Join(os.TempDir(), "vdir") vdirPath := "/vdir" user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath, + }, VirtualPath: vdirPath, - MappedPath: mappedPath, }) err := os.MkdirAll(mappedPath, 0777) assert.NoError(t, err) @@ -2924,38 +4618,6 @@ func TestResolveVirtualPaths(t *testing.T) { assert.Equal(t, filepath.Join(user.GetHomeDir(), "/vdir1/a.txt"), f) } -func TestVirtualFoldersExcludeQuota(t *testing.T) { - user := getTestUser(true) - mappedPath := filepath.Join(os.TempDir(), "vdir") - vdirPath := "/vdir/sub" - vSubDirPath := path.Join(vdirPath, "subdir", "subdir") - vSubDir1Path := path.Join(vSubDirPath, "subdir", "subdir") - user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ - VirtualPath: vdirPath, - MappedPath: mappedPath, - ExcludeFromQuota: false, - }) - user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ - VirtualPath: vSubDir1Path, - MappedPath: mappedPath, - ExcludeFromQuota: false, - }) - user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ - VirtualPath: vSubDirPath, - MappedPath: mappedPath, - ExcludeFromQuota: true, - }) - - assert.False(t, user.IsFileExcludedFromQuota("/file")) - assert.False(t, user.IsFileExcludedFromQuota(path.Join(vdirPath, "file"))) - assert.True(t, user.IsFileExcludedFromQuota(path.Join(vSubDirPath, "file"))) - assert.True(t, user.IsFileExcludedFromQuota(path.Join(vSubDir1Path, "..", "file"))) - assert.False(t, user.IsFileExcludedFromQuota(path.Join(vSubDir1Path, "file"))) - assert.False(t, user.IsFileExcludedFromQuota(path.Join(vSubDirPath, "..", "file"))) - // we check the parent dir for a file - assert.False(t, user.IsFileExcludedFromQuota(vSubDirPath)) -} - func TestUserPerms(t *testing.T) { user := getTestUser(true) user.Permissions = make(map[string][]string) @@ -3168,6 +4830,50 @@ func TestUserFiltersIPMaskConditions(t *testing.T) { assert.True(t, user.IsLoginFromAddrAllowed("invalid")) } +func TestGetVirtualFolderForPath(t *testing.T) { + user := getTestUser(true) + mappedPath1 := filepath.Join(os.TempDir(), "vpath1") + mappedPath2 := filepath.Join(os.TempDir(), "vpath1") + mappedPath3 := filepath.Join(os.TempDir(), "vpath3") + vdirPath := "/vdir/sub" + vSubDirPath := path.Join(vdirPath, "subdir", "subdir") + vSubDir1Path := path.Join(vSubDirPath, "subdir", "subdir") + user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, + VirtualPath: vdirPath, + }) + user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vSubDir1Path, + }) + user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath3, + }, + VirtualPath: vSubDirPath, + }) + folder, err := user.GetVirtualFolderForPath(path.Join(vSubDirPath, "file")) + assert.NoError(t, err) + assert.Equal(t, folder.MappedPath, mappedPath3) + _, err = user.GetVirtualFolderForPath("/file") + assert.Error(t, err) + folder, err = user.GetVirtualFolderForPath(path.Join(vdirPath, "/file")) + assert.NoError(t, err) + assert.Equal(t, folder.MappedPath, mappedPath1) + folder, err = user.GetVirtualFolderForPath(path.Join(vSubDirPath+"1", "file")) + assert.NoError(t, err) + assert.Equal(t, folder.MappedPath, mappedPath1) + _, err = user.GetVirtualFolderForPath("/vdir/sub1/file") + assert.Error(t, err) + // we check the parent dir + folder, err = user.GetVirtualFolderForPath(vdirPath) + assert.Error(t, err) +} + func TestSSHCommands(t *testing.T) { usePubKey := false user, _, err := httpd.AddUser(getTestUser(usePubKey), http.StatusOK) @@ -3279,7 +4985,7 @@ func TestBasicGitCommands(t *testing.T) { printLatestLogs(10) } - err = waitQuotaScans() + err = waitQuotaScans(1) assert.NoError(t, err) user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) @@ -3405,6 +5111,24 @@ func TestSCPUploadFileOverwrite(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, testFileSize, fi.Size()) } + // now create a simlink via SFTP, replace the symlink with a file via SCP and check quota usage + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + err = client.Symlink(testFileName, testFileName+".link") + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, testFileSize, user.UsedQuotaSize) + assert.Equal(t, 1, user.UsedQuotaFiles) + } + err = scpUpload(testFilePath, remoteUpPath+".link", true, false) + assert.NoError(t, err) + user, _, err = httpd.GetUserByID(user.ID, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, testFileSize*2, user.UsedQuotaSize) + assert.Equal(t, 2, user.UsedQuotaFiles) + err = os.Remove(localPath) assert.NoError(t, err) err = os.Remove(testFilePath) @@ -3529,8 +5253,10 @@ func TestSCPVirtualFolders(t *testing.T) { mappedPath := filepath.Join(os.TempDir(), "vdir") vdirPath := "/vdir" u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath, + }, VirtualPath: vdirPath, - MappedPath: mappedPath, }) err := os.MkdirAll(mappedPath, 0777) assert.NoError(t, err) @@ -3557,6 +5283,8 @@ func TestSCPVirtualFolders(t *testing.T) { _, err = httpd.RemoveUser(user, http.StatusOK) assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK) + assert.NoError(t, err) err = os.RemoveAll(testBaseDirPath) assert.NoError(t, err) err = os.RemoveAll(testBaseDirDownPath) @@ -3579,13 +5307,20 @@ func TestSCPVirtualFoldersQuota(t *testing.T) { mappedPath2 := filepath.Join(os.TempDir(), "vdir2") vdirPath2 := "/vdir2" u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath1, + }, VirtualPath: vdirPath1, - MappedPath: mappedPath1, + QuotaFiles: -1, + QuotaSize: -1, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ - VirtualPath: vdirPath2, - MappedPath: mappedPath2, - ExcludeFromQuota: true, + BaseVirtualFolder: vfs.BaseVirtualFolder{ + MappedPath: mappedPath2, + }, + VirtualPath: vdirPath2, + QuotaFiles: 0, + QuotaSize: 0, }) err := os.MkdirAll(mappedPath1, 0777) assert.NoError(t, err) @@ -3609,6 +5344,11 @@ func TestSCPVirtualFoldersQuota(t *testing.T) { remoteUpPath1 := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, vdirPath1) remoteDownPath2 := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", vdirPath2)) remoteUpPath2 := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, vdirPath2) + // we upload two times to test overwrite + err = scpUpload(testBaseDirPath, remoteUpPath1, true, false) + assert.NoError(t, err) + err = scpDownload(testBaseDirDownPath, remoteDownPath1, true, true) + assert.NoError(t, err) err = scpUpload(testBaseDirPath, remoteUpPath1, true, false) assert.NoError(t, err) err = scpDownload(testBaseDirDownPath, remoteDownPath1, true, true) @@ -3623,9 +5363,27 @@ func TestSCPVirtualFoldersQuota(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles) assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize) + folder, _, err := httpd.GetFolders(0, 0, mappedPath1, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, expectedQuotaSize, f.UsedQuotaSize) + assert.Equal(t, expectedQuotaFiles, f.UsedQuotaFiles) + } + folder, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK) + assert.NoError(t, err) + if assert.Len(t, folder, 1) { + f := folder[0] + assert.Equal(t, expectedQuotaSize, f.UsedQuotaSize) + assert.Equal(t, expectedQuotaFiles, f.UsedQuotaFiles) + } _, err = httpd.RemoveUser(user, http.StatusOK) assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath2}, http.StatusOK) + assert.NoError(t, err) err = os.RemoveAll(testBaseDirPath) assert.NoError(t, err) err = os.RemoveAll(testBaseDirDownPath) @@ -3634,7 +5392,7 @@ func TestSCPVirtualFoldersQuota(t *testing.T) { assert.NoError(t, err) err = os.RemoveAll(mappedPath1) assert.NoError(t, err) - err = os.RemoveAll(mappedPath1) + err = os.RemoveAll(mappedPath2) assert.NoError(t, err) } @@ -4425,17 +6183,25 @@ func waitForActiveTransfer() { } } -func waitQuotaScans() error { - time.Sleep(100 * time.Millisecond) - scans, _, err := httpd.GetQuotaScans(http.StatusOK) - if err != nil { - return err - } - for len(scans) > 0 { - time.Sleep(100 * time.Millisecond) - scans, _, err = httpd.GetQuotaScans(http.StatusOK) - if err != nil { - return err +func waitQuotaScans(kind int) error { + for { + time.Sleep(50 * time.Millisecond) + var activeScans int + if kind == 1 { + scans, _, err := httpd.GetQuotaScans(http.StatusOK) + if err != nil { + return err + } + activeScans = len(scans) + } else { + scans, _, err := httpd.GetFoldersQuotaScans(http.StatusOK) + if err != nil { + return err + } + activeScans = len(scans) + } + if activeScans == 0 { + break } } return nil @@ -4567,7 +6333,9 @@ func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse bool) []byt if nonJSONResponse { extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...) } else { - extAuthContent = append(extAuthContent, []byte("echo '{\"username\":\"\"}'\n")...) + json, _ := json.Marshal(user) + quoteJson := strconv.Quote(string(json)) + extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%v'\n", quoteJson))...) } extAuthContent = append(extAuthContent, []byte("fi\n")...) return extAuthContent diff --git a/sftpd/transfer.go b/sftpd/transfer.go index 4e0d2a65..7d7b5a9d 100644 --- a/sftpd/transfer.go +++ b/sftpd/transfer.go @@ -28,26 +28,26 @@ var ( // Transfer contains the transfer details for an upload or a download. // It implements the io Reader and Writer interface to handle files downloads and uploads type Transfer struct { - file *os.File - writerAt *vfs.PipeWriter - readerAt *pipeat.PipeReaderAt - cancelFn func() - path string - start time.Time - bytesSent int64 - bytesReceived int64 - user dataprovider.User - connectionID string - transferType int - lastActivity time.Time - protocol string - transferError error - minWriteOffset int64 - initialSize int64 - lock *sync.Mutex - isNewFile bool - isFinished bool - isExcludedFromQuota bool + file *os.File + writerAt *vfs.PipeWriter + readerAt *pipeat.PipeReaderAt + cancelFn func() + path string + start time.Time + bytesSent int64 + bytesReceived int64 + user dataprovider.User + connectionID string + transferType int + lastActivity time.Time + protocol string + transferError error + minWriteOffset int64 + initialSize int64 + lock *sync.Mutex + isNewFile bool + isFinished bool + requestPath string } // TransferError is called if there is an unexpected error. @@ -189,11 +189,17 @@ func (t *Transfer) updateQuota(numFiles int) bool { if t.file == nil && t.transferError != nil { return false } - if t.isExcludedFromQuota { - return false - } if t.transferType == transferUpload && (numFiles != 0 || t.bytesReceived > 0) { - dataprovider.UpdateUserQuota(dataProvider, t.user, numFiles, t.bytesReceived-t.initialSize, false) //nolint:errcheck + vfolder, err := t.user.GetVirtualFolderForPath(t.requestPath) + if err == nil { + dataprovider.UpdateVirtualFolderQuota(dataProvider, vfolder.BaseVirtualFolder, numFiles, //nolint:errcheck + t.bytesReceived-t.initialSize, false) + if vfolder.IsIncludedInUserQuota() { + dataprovider.UpdateUserQuota(dataProvider, t.user, numFiles, t.bytesReceived-t.initialSize, false) //nolint:errcheck + } + } else { + dataprovider.UpdateUserQuota(dataProvider, t.user, numFiles, t.bytesReceived-t.initialSize, false) //nolint:errcheck + } return true } return false diff --git a/sftpgo.json b/sftpgo.json index 078e273c..6f3f2699 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -38,7 +38,7 @@ "password": "", "sslmode": 0, "connection_string": "", - "users_table": "users", + "sql_tables_prefix": "", "manage_users": 1, "track_quota": 2, "pool_size": 0, diff --git a/static/vendor/fontawesome-free/css/all.min.css b/static/vendor/fontawesome-free/css/all.min.css index 3158702f..3d28ab20 100644 --- a/static/vendor/fontawesome-free/css/all.min.css +++ b/static/vendor/fontawesome-free/css/all.min.css @@ -1,5 +1,5 @@ /*! - * Font Awesome Free 5.10.2 by @fontawesome - https://fontawesome.com + * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) */ -.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;font-display:auto;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file +.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\f95b"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\f952"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-disease:before{content:"\f7fa"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-faucet:before{content:"\f905"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\f907"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\f95c"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\f95d"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-hands-wash:before{content:"\f95e"}.fa-handshake:before{content:"\f2b5"}.fa-handshake-alt-slash:before{content:"\f95f"}.fa-handshake-slash:before{content:"\f960"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-head-side-cough:before{content:"\f961"}.fa-head-side-cough-slash:before{content:"\f962"}.fa-head-side-mask:before{content:"\f963"}.fa-head-side-virus:before{content:"\f964"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-house-user:before{content:"\f965"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\f913"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\f955"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-house:before{content:"\f966"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\f967"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\f91a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\f956"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-arrows:before{content:"\f968"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\f91e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-slash:before{content:"\f969"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pump-medical:before{content:"\f96a"}.fa-pump-soap:before{content:"\f96b"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-shield-virus:before{content:"\f96c"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopify:before{content:"\f957"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\f96e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\f96f"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-store-alt-slash:before{content:"\f970"}.fa-store-slash:before{content:"\f971"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\f972"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\f941"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\f949"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-virus:before{content:"\f974"}.fa-virus-slash:before{content:"\f975"}.fa-viruses:before{content:"\f976"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.fab,.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/static/vendor/fontawesome-free/package.json b/static/vendor/fontawesome-free/package.json deleted file mode 100644 index 8fb4c534..00000000 --- a/static/vendor/fontawesome-free/package.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "_from": "@fortawesome/fontawesome-free@5.10.2", - "_id": "@fortawesome/fontawesome-free@5.10.2", - "_inBundle": false, - "_integrity": "sha512-9pw+Nsnunl9unstGEHQ+u41wBEQue6XPBsILXtJF/4fNN1L3avJcMF/gGF86rIjeTAgfLjTY9ndm68/X4f4idQ==", - "_location": "/@fortawesome/fontawesome-free", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "@fortawesome/fontawesome-free@5.10.2", - "name": "@fortawesome/fontawesome-free", - "escapedName": "@fortawesome%2ffontawesome-free", - "scope": "@fortawesome", - "rawSpec": "5.10.2", - "saveSpec": null, - "fetchSpec": "5.10.2" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.10.2.tgz", - "_shasum": "27e02da1e34b50c9869179d364fb46627b521130", - "_spec": "@fortawesome/fontawesome-free@5.10.2", - "_where": "/Users/DANGER_DAVID/Sites/startbootstrap-themes/startbootstrap-sb-admin-2", - "author": { - "name": "Dave Gandy", - "email": "dave@fontawesome.com", - "url": "http://twitter.com/davegandy" - }, - "bugs": { - "url": "http://github.com/FortAwesome/Font-Awesome/issues" - }, - "bundleDependencies": false, - "contributors": [ - { - "name": "Brian Talbot", - "url": "http://twitter.com/talbs" - }, - { - "name": "Travis Chase", - "url": "http://twitter.com/supercodepoet" - }, - { - "name": "Rob Madole", - "url": "http://twitter.com/robmadole" - }, - { - "name": "Geremia Taglialatela", - "url": "http://twitter.com/gtagliala" - }, - { - "name": "Mike Wilkerson", - "url": "http://twitter.com/mw77" - } - ], - "dependencies": {}, - "deprecated": false, - "description": "The iconic font, CSS, and SVG framework", - "engines": { - "node": ">=6" - }, - "homepage": "https://fontawesome.com", - "keywords": [ - "font", - "awesome", - "fontawesome", - "icon", - "svg", - "bootstrap" - ], - "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", - "main": "js/fontawesome.js", - "name": "@fortawesome/fontawesome-free", - "repository": { - "type": "git", - "url": "git+https://github.com/FortAwesome/Font-Awesome.git" - }, - "style": "css/fontawesome.css", - "version": "5.10.2" -} diff --git a/static/vendor/fontawesome-free/svgs/solid/folder.svg b/static/vendor/fontawesome-free/svgs/solid/folder.svg new file mode 100644 index 00000000..c9607689 --- /dev/null +++ b/static/vendor/fontawesome-free/svgs/solid/folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/vendor/fontawesome-free/webfonts/fa-solid-900.eot b/static/vendor/fontawesome-free/webfonts/fa-solid-900.eot index 4101e3180f1af636f3fd14c5f7cfad46212e2697..d3b77c223afc9a0a1af914936219da2b001a010f 100644 GIT binary patch delta 20044 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 J`F{}Y{{qr0>7xJu delta 9749 zcmZvi3w%_?y~lt5*~i)4bIxXyedV3aI{_pi+3W@gut0zyh*3}w5fXVAl}CzE5NS7B zw5X_H&|r%ilvY%<^uoPCOEoGg^@%N2+R`g3T3Tsu+G2|>P44e(z}nBJknhZyng9If zf9CO@nUg&DBjfMKbZy-q6&CzKZPD^_1*w0&p$K5Q(|TUhUpcrXvA|u8k*GlwH4lEO z;*L~m%SRU_H+3xA0riIjC3B}WPML~fwpW>sFr9z#?5di{-~RNkR5p+4mc=Vt+lO6U zdkf&(2k5J>X}zI+a1b`iXR_RN&GMU$4IP=8TQFe zK1zT=N!yB!% z_o)ZuZKhpNT_aSb_M%nK_F~rpl_y_zty7gn@>Mrdta;g#CFtzOnQrMD?88WT3v)X6 zzN&_4lIZGp&+WR{b4DV2XX@{YB=+W-lSIj2SBrOX*X!Qpwg}J_OMAdZMSI^#+bUk^ z1T3>iM4@YK`s!g!S>+bFmMws1IaAhCF6^FW3U8*k3!C?D%Se~5XZ(%Q*VXSI*HsmG zze@|2?j0WtO4r=bRC&2;duVLeFGD}G{o{?=m-<-!X4DZ!>Q-JI!yJ>&;utb!LaT%xpEUGOsk} zn^&0g%w}_@d7;^4PBp8|QnSP?HjB)NSzzXyd1kJeV`iIKX3+GT8D_faGrgu^x=k%< z{M|Tdd}e%P95a4xylec_c+Gg(=rdk04jB85UgLj^ea0T+8DqEcl<}mo%h+i=Zaik} zFdj7?F}53Bqr+Hjv>DeJ%Z#PQ5@WH^Vq9fhY0Nj~8kZTf zjEjsJ#zZ4wj5Wp>QDc-b!bnyac}9+rWrU1$!!SICW;pb-`akq9^grmI>%Z53r+=m& z*FV&Mr61Kl(BIdO=s(xr*5A~Bsvpu{*I(0L(O=eI();uS`dH93 z->P@%TlCF(r@mfasV~;A))(nl>9h1H`WSt*9@Fde5qkOF&DmR3hH&bqMwjC%sVT6x zJx`P5Os01+i<8b;Xu`#%3{HmyfZfG;-U&D@-U74)Il-yGHR7hc`y}Ve0WQY%gyqIY zz>Jb@Tu%*LL%D=9zDBYiNN4?Y%BJ@N8O-;$0m%R>1XwZ1MuWS^5g^n-P6J^IXM#{L ztC}z`dnb@HhjY_T_5pb$e*;j!yn@|8Btj@3*$xy^rjX@@XMiG>7g2xFF`&4CbdW4+1k9$O&L36TuoU>@6=$Nc0x3eKa_D@w>5au%4s7Pyk>E7t=Hn0Hks zq0)toz@mDf#Yb4yLfKZzUcCWW+y*RR<4bk}OPk3tU>W0OM}cdWkba=8gY*H{x(M}N zyPe~IodH}&1=k$`t}h{MXgMn`r}7mMvJ+UzcqL_4vAmt>Z;;iQWFK$?-|~iyKnG=R zq~aT?=cYEocn$N`P|sRE-&_r>1L*;7Vfih`f%RKC{@)xyHjz`nw`P((>=riGX^;+b z5V$o!`heS*ciR$j82I)AU;~ZVK-mpcc6&Y93fwV=F#Vlvz{V1C0JyUOxN9ES4cyI_ z+Y|-vVcmP#KJGmU+;;@n%m%hl+5I%)epc#Y<*uW^))9ot9-x8;_K|+z!Tmrt)7@u) zZB+VDBRK|qkNUQ=-opX18F+*;k5KQU9Y7D+u^xDg`X8h0<4t_goejYE?PNdjgBEfM zc!CvnZ3UiW!%x+dgTT{KvI*E-O(^rsOyF56fA$dY!zI9;IphTJU#z#6WqX;wZzu5I ztp7g?fL<5r0QP5+r`M3Llci7Im4df*I z|L|7eJ%h0DXKjRy{=A7$*%2x~vXLYg_yjAQI17BbfH3c~OtOTq z!f*S4lbe9wv60_V_V-MG&h#nrhh4xQneLwn{HY%J^K#%XjlgN9zo2nn90LBj4fq>K z4{)Xv_%h1z|MDd8_wB$xnhCq+pKNr14GvPl-~nYQl1pb+}O_#bg8CnS{dLGmtbsr%{Qof$V{# zM@b(fqnR-FQ?Z{4{YQ9`WLeNo+99C?ISdI?F1#O-86le?SsNkQKEgWL)Stt=+Zf+XWfk zOin;XR1ns`fcYc&5>MB@K=wc`Dj^3Tvl!2!@{6gsnGH2}5GuTc$}U|CneBqiSwhZ2 zE@S=6n;>)TgbmDN**xmvo+4Kqhs;kvu4LT>ry*BSe&KG&q6nm=h2!6H3eq}kmP`xt>t&TkY)1067Ke+z7c9gz>izKsI!evyj`5L+)sXe5ZjNglyam zxhq-CfQs%Wn`qr8T7S<;$i0m3TTc2Ro7*5;MiA!T&xX1>AX}Rt4@3y%9^6FO0QUmf zwgK``CZY20^7(taAln%~OkEGN{E_vLM{^-Phafv>NOH$p5N-(a*iOjf%>2F!@`EVk z34?5e?4p6YPC}kM26?In@^m9)cN^px)_InSf7k-qLw-b~{>um1YfqhFW#4Yde=i|# zL3+VE9$)Uc1rY1L_dInSpxlA=9RHuxLw>@H7pUMxDt++;q;E6iB?`Q>2l6s2yu20i z3g7CL?T~{J$g9kIbvZc&d2J`;bsGFS8#+Y8ewrYMA#bq3H<|w?jem=J-fkfKAn)`* z-pwV956|QHAMS&^M`b^2hx~j4ISo0&hJMjVPC(v22>F0Ue!vPxn+VH)RYJ}{e%%Q< z)(rVgf}DkXcnorU6XYYxeoTWuseqiYlbw)HTOf4j@)^s1>m&Oize~Cxzwd;6PU}x? zh5VmEsHC5k(vQoZPC@?63a1m0FGfKAI+O5i&a^qr z5h(13l7mpTD0vIY-UH=qhtg(3x%NSMc0(ogOelRP?;LZWOgq^K*8`Q~@vD z1$&?(o1hAFp^9Bds*)0@Qd(4c9IA|sR8Y8*N`@Ka5Y+J9P$O7=K_fW=HIfZhQBl<~ zsOko&QIs3C0jg#R*$x%u3q?;u#hRevARD3TEzUxXX8zbHR0HdbtA`qYAPJRN05!1> zYElQ(WC}L6Kuu|en##OsRNRyxtUP@e)Qk~?Z#8ot)GQY{3UzTiR5PEOPeEPM2{pSK zYK{dPyPWx#Z-SajIeJ8Og&k@>biY&+ zO`WTo$ySd44fCLGI11I#L3Tpj*aCHvL8xHOMsgBr?KY^JY23|yQ0taM-NN!)c0;XS zPfkO9^B~l>Sbl38)NKc#zC9Ca1Isp?g1VjY9n|$5mTxpjKh&Kx@-E8Vy^9}dHqnZE z*ucHMu8&7vHv1sIdT_%JIE%`nvD%t&og?_wb9yvh8Vf1ZH`mu!q@@p@e+Ws!mn}U5 zJvpVNu~PeUGk!nxAkMJ1(Ba>Ef7M5x?ioQb;-Lu=tCG6ta4E`@K=`3|wrqK4i;VJU zxtcp+R2vT2&x|eaysf#*J=(~-3}ctm&cp1>W1hd7cJZT4r?l|n)Ns^dEbXib1)Rl2 z<;Bjf?>=0A;pD0XRg)(zTsWzPpB^g6xoBraZf-^H@>D1-3pXsRu4b_BYZ6AZo)Wng z(NxF=^B=j)VS{DkL~|BZNm+D)FjdEByo@F6u#mGR7A-4s z2INbvvB9Tb?QnX$Ay2la*6j|rdAzn!j%=f?Y-v+%#Ivy|94-pWGlexxOUm2KEXOFD z*V7huyK5;Q3V1zE$6}9f++7M_1Mxmj&Z>DNBok3DfWAcOnM-a&_k$qy4^!<~}S5zd6vC%B4WpdvA<&wYas& zW~-6>H^+vxx@fE>lqX#+ALlg|rxHPdKiglex!sy?dOQ)I?z0}-6U>zAc1u;ek18~G zWXwE$@nU`6m`Ez=OO@??>Eo`-sgKus=By()ekP)>p|$c4C3lm6wfI z@mQ!pYRk)F@mN0dYUAU@60eQNG$4lNT|>lSFUU>)8)&x7kjGh z89qIbrlM)p_Pj{CeS}_Mm&LAJ-$VQ$G|X#Wk>)qQr59$3KDzm7m(O%7R(R-L)>Y0l z{iBzC>8?EAv$y(si%nUl<`kefHP?pvI9QvSb%oXf87o#Fr}|vd3)59|AU8Mgd5_f- z1D#gb^M#e8d;{E6a{~i?f!qi?i$A?zrb$^&vIm8z>agj5aUTlqVO*j?`$7%|MOJHb))`YA9<8$m>`dt1PWo4E{YyLgk z5>NiS{C}>WlHtqNoNwoREO$Jj1vEp`vQ(x&6d9SAJvLsMJtmaOG~Hge+gq6m=cdB$ zR9ItWyIY2GlBwwHLTgevLluU?>8YIX*Fvd(L}Rl7E&t*IV`OCD9jO^RyVRv;q;i{5 z4TQfIR;Hd)3Q|u)X-i_N*F7W{|Igy?AVjSO9nMUq z7F%jc42@1W#9D4qF3}BNr^oxG zp3j-wW6f)+@EN*K^9-DkgeJQ>nJPK0Q8=Ler9{v74h$M*(6=Wg+7igkr|dxAW{*bO zc1(~LJiUu%$Wz0#LRvT;OS^qHNg$bW3FT$<2EjmqGZ{0L!PGGf5>KwFDBq!Hn@(e(fAKv9qe~z6WNVt!m@5uh ze9t{HEhAR2?Bc@lGJ(!BjgzY`JsztGm!}R!);i(b$`^I6 z%em89yKPWDw3;0_Lff97;C3I88SWhSw$T^Yvuj*w?&|_xZ@^ohNF+K_eI+*e@k8#? z9QQB8sl+wIq!fh zGq1Ra|3lU>W@y)thvWHqb4%8j%+1S>C;#<4aJ^P)x9NIlgpDgo3N$UGcSkaO85zEs zp{T`X%PTC*ONE^*)ou1tS4h(eL@8m_t*MQ$Z$@3zbNS_-Xx)sjo}M-&5O8~n9}EO^ zv)FS-sZ-OO1K%%omM~shTpaZIf~l~ArS3pLwilZ^Yh-iA__KSb>1uPBh>Y6^D z7Q@#5UHX+1Ney188* z`r|P=@vt>>!#ucZbo(6sc+6UCajrZ@)=4$2)mo);yrS@qOEl+2$%16XSi4A7W$1Fd z!{xDg#yG`UnSDW?%5iw?fnl09EMWII@(rI}@KuH=ha*Ro*14R4EQ*g|HJ8JFS-7%F z94$@r>~p7#4e}R~mCG7zURPH3wBe34M`=7STI`&dVRt(V-8}ob3!QFzP$CJJYv6b= zm8(qVDh{E;o-t9YD~*f~v*Pe+*;y{H8n>*{C)wHD-q1I5)Q-yHd7NN6_Z9vB%OPo=jhspRdVZSu1g8*lZbYE)4NHTnaMh)t#0#3^>xQp(eAx36+@6>>XtiY?8`6Rpr*5D2CF^E1-YBiT-;QUjkQGIBGx zUFX%RIr$^49g3f+5a1h)47qp4iV{e;l>lw_c+1=1adu7Z>RaXt{N+ zbh|cm(i-T0>Or@b(A+(#Ft@~7xnkWva!N}^KB&7~IUbj{nwz8BdqwP5scS~N6~6Bo zRlpUR?Wa+pOI5Ilw=^4fn)AnKe!}H&(9v;$cx{?jg4WS@g5tang#+Q(d6zwOQZ9>z z&bztK-CkQ>#Anl-c0IfNf}p6;kF6f9RO9_TPdMifro6%Il*^i)IxNbIHq+BOzuF7vxj(CmQ@^+9IRx>{N%e_k!QOgVux z9(0^En1OR7Z$G^6q=Ilfb>^uFG%01KKVGg0 zt!||u&7KTf(3a-7K0`8c=xUWSW4S{)%jZ{kE;%Pr?aB32c|6qv$v;h*KBZ^+l! z*y?s);;EQl?o^KD8BWDXkV6&MJJP5w!+ps)iE0n4=6b3}v*y4%tV$mN+u)hO{i(}* z7LO#8fVg$4w0haPZCcAXH67?8Z1kw-F4FY7-9PVCIT!z=!>P2<8?W|9>^{ehxyl+% znyszl;n2G5I`3+Ax;@(Ln}(8(*`w{6-LtI2>9ZI3TQ`o@l;g&nl)>44CF8p%-l~dv z2Om@;e7(PYf)1{C?9-~jQPGDS-iaq*7R}>PFUrA>jgxqo&Ys(GFksz~IZfi-S1p#D z?icqcSCW&QV~{!xMq}~t7|pHn4NqsM$H-T9&2eez!qO3rki$pLMHM{uq{U_wWe)7i zESeEp#N}tVHIx)oj;gL6RasEdV6)p@_DJOD*XlSzS49e|stO}LYE~?>s3_H*G;-6@)i+$bYUP+n-N;ca?_K(-DpK-d@5bM%?e=`^w@tP++NRj1 z+NRl>Y}0KQ+Gf~h;(psjwpq4|dncVz|0@~zjeUYWVV{T$`y~5hd!v0ycg-Kwk={A~ zr~a1FyI9%!RBy~-Yj*c;^3v~V&bHQ@uWw!6yCcok>ge_aZNqve1#Qd4`rIV^3oAN2 XJ{crLtd!06Lifr{Tdwi-KW+MduUwU{ diff --git a/static/vendor/fontawesome-free/webfonts/fa-solid-900.svg b/static/vendor/fontawesome-free/webfonts/fa-solid-900.svg index 228a9b67..7742838b 100644 --- a/static/vendor/fontawesome-free/webfonts/fa-solid-900.svg +++ b/static/vendor/fontawesome-free/webfonts/fa-solid-900.svg @@ -1,12 +1,12 @@ -Created by FontForge 20190801 at Thu Aug 22 14:41:09 2019 +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 By Robert Madole Copyright (c) Font Awesome @@ -23,15 +23,15 @@ Copyright (c) Font Awesome bbox="-0.983398 -64.9834 640.104 448.427" underline-thickness="25" underline-position="-50" - unicode-range="U+0020-F897" + unicode-range="U+0020-F976" /> +d="M470.38 446.49c2.59277 0.816406 6.90234 1.48047 9.62012 1.48047c17.6475 0 31.9834 -14.3232 32 -31.9707v-352c0 -35.3496 -43 -64 -96 -64s-96 28.6602 -96 64s43 64 96 64c8.95898 -0.0488281 23.2949 -1.80957 32 -3.92969v184.609l-256 -75v-233.68 +c0 -35.3398 -43 -64 -96 -64s-96 28.6602 -96 64s43 64 96 64c8.95801 -0.0507812 23.2939 -1.80664 32 -3.91992v261.41c0.0078125 12.958 10.0479 26.626 22.4102 30.5098z" /> +d="M256 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM348.49 127c1.94043 2.4209 3.51465 6.90332 3.51465 10.0059c0 4.24512 -2.69043 9.84277 -6.00488 12.4941l-58 42.5v144c0 8.83203 -7.16797 16 -16 16h-32 +c-8.83203 0 -16 -7.16797 -16 -16v-155.55v-0.00488281c0 -10.6074 6.71973 -24.5957 15 -31.2256l67 -49.7197v0c2.41895 -1.93555 6.89746 -3.50586 9.99512 -3.50586c4.24512 0 9.84277 2.69043 12.4951 6.00586l20 25v0z" /> +d="M480.07 352c88.2939 -0.0263672 159.952 -71.7061 159.952 -160c0 -88.3203 -71.6797 -160 -160 -160c-37.1016 0 -88.291 21.5039 -114.263 48h-91.5195c-25.9717 -26.4961 -77.1611 -48 -114.263 -48c-88.3203 0 -160 71.6797 -160 160s71.6797 160 160 160h0.0224609 +h320.07zM248 180v24c0 6.62402 -5.37598 12 -12 12h-52v52c0 6.62402 -5.37598 12 -12 12h-24c-6.62402 0 -12 -5.37598 -12 -12v-52h-52c-6.62402 0 -12 -5.37598 -12 -12v-24c0 -6.62402 5.37598 -12 12 -12h52v-52c0 -6.62402 5.37598 -12 12 -12h24 +c6.62402 0 12 5.37598 12 12v52h52c6.62402 0 12 5.37598 12 12zM464 104c22.0801 0 40 17.9199 40 40s-17.9199 40 -40 40s-40 -17.9199 -40 -40s17.9199 -40 40 -40zM528 200c22.0801 0 40 17.9199 40 40s-17.9199 40 -40 40s-40 -17.9199 -40 -40s17.9199 -40 40 -40z +" /> +d="M505.12 428.906c6.95508 -32.2031 6.95508 -57.4062 6.86133 -82.6094c0 -102.688 -55.4375 -164.781 -128.035 -211.094v-104.438c0 -16.3594 -11.8789 -35.5625 -26.5078 -42.8594l-98.7275 -49.3906c-2.81934 -1.27441 -7.61621 -2.40137 -10.707 -2.51562 +c-13.2471 0.00195312 -24.002 10.7539 -24.0059 24v103.844l-22.4746 -22.4688c-13.1211 -13.1562 -34.1211 -11.1875 -45.2773 0l-50.9043 50.9062c-12.9961 12.9922 -11.3652 33.8887 0 45.25l22.4746 22.4688h-103.811c-13.2461 0.00195312 -24.001 10.7539 -24.0059 24 +c0.111328 3.09082 1.23828 7.88574 2.51562 10.7031l49.4355 98.8125c7.33008 14.6094 26.5391 26.4688 42.8867 26.4844h104.215c46.2168 72.7969 108.122 128 211.354 128c25.0996 0 50.3086 0 82.5059 -6.90625c5.54883 -1.1875 11.0176 -6.65625 12.207 -12.1875z +M384.04 280c22.0732 0.0078125 39.9971 17.9277 40.0098 40c0 22.0801 -17.9199 40 -40 40s-40 -17.9199 -40 -40c0 -22.0742 17.916 -39.9951 39.9902 -40z" /> @@ -1159,11 +1160,11 @@ c10.9004 -8.7998 22.8008 -17.0996 35.4004 -24.8994c5.7998 -3.5 13.2998 -1.60059 c6.59961 0 12 5.40039 12 12zM0 328c0 13.2998 10.7002 24 24 24h280v-320h-280c-13.2998 0 -24 10.7002 -24 24v272zM58.9004 111.9c-2.60059 -7.80078 3.19922 -15.9004 11.3994 -15.9004h22.9004c5.2998 0 10 3.59961 11.5 8.7002l9.09961 31.7998h60.2002 l9.40039 -31.9004c1.40137 -4.74316 6.55273 -8.59668 11.5 -8.59961h22.8994c8.2998 0 14 8.09961 11.4004 15.9004l-57.5 169.1c-1.7002 4.7998 -6.2998 8.09961 -11.4004 8.09961h-32.5c-5.2002 0 -9.7002 -3.19922 -11.3994 -8.09961z" /> +d="M480 288c17.6641 0 32 -14.3359 32 -32v-288c0 -17.6641 -14.3359 -32 -32 -32h-320c-17.6641 0 -32 14.3359 -32 32v448c0 17.6641 14.3359 32 32 32h242.75c7.31348 -0.000976562 17.4473 -4.19922 22.6201 -9.37012l45.25 -45.25 +c5.17676 -5.17285 9.37891 -15.3115 9.37988 -22.6299v-82.75zM288 16v32c0 8.83203 -7.16797 16 -16 16h-32c-8.83203 0 -16 -7.16797 -16 -16v-32c0 -8.83203 7.16797 -16 16 -16h32c8.83203 0 16 7.16797 16 16zM288 144v32c0 8.83203 -7.16797 16 -16 16h-32 +c-8.83203 0 -16 -7.16797 -16 -16v-32c0 -8.83203 7.16797 -16 16 -16h32c8.83203 0 16 7.16797 16 16zM416 16v32c0 8.83203 -7.16797 16 -16 16h-32c-8.83203 0 -16 -7.16797 -16 -16v-32c0 -8.83203 7.16797 -16 16 -16h32c8.83203 0 16 7.16797 16 16zM416 144v32 +c0 8.83203 -7.16797 16 -16 16h-32c-8.83203 0 -16 -7.16797 -16 -16v-32c0 -8.83203 7.16797 -16 16 -16h32c8.83203 0 16 7.16797 16 16zM416 256v64h-48c-8.83203 0 -16 7.16797 -16 16v48h-160v-128h224zM64 320c17.6641 0 32 -14.3359 32 -32v-320 +c0 -17.6641 -14.3359 -32 -32 -32h-32c-17.6641 0 -32 14.3359 -32 32v320c0 17.6641 14.3359 32 32 32h32z" /> +d="M320 416v-96h-64v96c0 17.6641 14.3359 32 32 32s32 -14.3359 32 -32zM368 288c8.83203 0 16 -7.16797 16 -16v-32c0 -8.83203 -7.16797 -16 -16 -16h-16v-32c-0.0107422 -72.1074 -57.3555 -142.354 -128 -156.8v-99.2002h-64v99.2002 +c-70.6445 14.4463 -127.989 84.6924 -128 156.8v32h-16c-8.83203 0 -16 7.16797 -16 16v32c0 8.83203 7.16797 16 16 16h352zM128 416v-96h-64v96c0 17.6641 14.3359 32 32 32s32 -14.3359 32 -32z" /> +d="M510.9 302.729l-68.2969 -286.823c-10.502 -44.1084 -55.8252 -79.9062 -101.166 -79.9062h-127.363c-29.7637 0 -71.5107 16.5547 -93.1855 36.9531l-108.298 101.92c-6.92383 6.53418 -12.542 19.5635 -12.542 29.083c0 22.0762 17.916 39.9922 39.9922 39.9922 +c8.7334 0 20.9922 -4.84961 27.3623 -10.8252l60.5928 -57.0254v0c0 22.6758 -5.22852 58.7256 -11.6699 80.4668l-42.6885 144.075c-0.90918 3.06934 -1.64746 8.1582 -1.64746 11.3594c0 22.083 17.9229 40.0059 40.0059 40.0059 +c16.4922 0 33.6768 -12.833 38.3594 -28.6465l37.1543 -125.395c0.975586 -3.29199 4.55469 -5.96484 7.98828 -5.96484c4.59863 0 8.33105 3.73242 8.33105 8.33105c0 0.582031 -0.117188 1.51172 -0.262695 2.0752l-50.3047 195.641 +c-0.696289 2.70703 -1.26172 7.17285 -1.26172 9.96875c0 22.0781 17.918 39.9961 39.9961 39.9961c17.1152 0 34.4678 -13.4521 38.7344 -30.0273l56.0947 -218.158c1.11035 -4.31934 5.63184 -7.82617 10.0918 -7.82617c4.69238 0 9.26562 3.73047 10.208 8.32715 +l37.6826 183.704c3.6416 17.6387 21.2139 31.9541 39.2246 31.9541c3.41309 0 8.82422 -0.835938 12.0781 -1.86426c19.8604 -6.2998 30.8623 -27.6738 26.6758 -48.085l-33.8389 -164.967c-0.0849609 -0.414062 -0.154297 -1.09375 -0.154297 -1.51758 +c0 -4.16797 3.38281 -7.55176 7.55176 -7.55176c3.29297 0 6.58398 2.59961 7.34668 5.80273l29.3975 123.459c4.03906 16.9619 21.4688 30.7285 38.9053 30.7285c22.0771 0 39.9941 -17.917 39.9941 -39.9941c0 -2.59277 -0.487305 -6.74316 -1.08789 -9.26562z" /> +d="M592 448c26.4961 0 48 -21.5039 48 -48v-320c0 -26.4961 -21.5039 -48 -48 -48h-240v-32h176c8.83203 0 16 -7.16797 16 -16v-32c0 -8.83203 -7.16797 -16 -16 -16h-416c-8.83203 0 -16 7.16797 -16 16v32c0 8.83203 7.16797 16 16 16h176v32h-240 +c-26.4961 0 -48 21.5039 -48 48v320c0 26.4961 21.5039 48 48 48h544zM576 96v288h-512v-288h512z" /> +d="M304 128c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM336 224c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM368 160c-8.83203 0 -16 7.16797 -16 16s7.16797 16 16 16 +s16 -7.16797 16 -16s-7.16797 -16 -16 -16zM336 128c-8.83203 0 -16 7.16797 -16 16s7.16797 16 16 16s16 -7.16797 16 -16s-7.16797 -16 -16 -16zM304 192c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM432 224 +c-8.83203 0 -16 7.16797 -16 16s7.16797 16 16 16s16 -7.16797 16 -16s-7.16797 -16 -16 -16zM384 208c0 8.83203 7.16797 16 16 16s16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16zM368 256c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16 +s-16 7.16797 -16 16s7.16797 16 16 16zM464 224c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM496 256c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM432 192 +c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM400 160c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM336 96c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16 +s-16 7.16797 -16 16s7.16797 16 16 16zM304 64c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM368 128c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM389.65 346.35 +c2.58691 -2.58691 4.6875 -7.65527 4.6875 -11.3145s-2.10059 -8.72852 -4.6875 -11.3154l-169.381 -169.37c-2.58691 -2.58691 -7.65527 -4.6875 -11.3145 -4.6875s-8.72852 2.10059 -11.3154 4.6875l-11.2998 11.3105c-2.58496 2.58594 -4.68262 7.65332 -4.68262 11.3096 +c0 3.65723 2.09766 8.72363 4.68262 11.3105l5.66016 5.66992c-17.6602 17.9219 -31.9961 52.8887 -32 78.0498c0 19.2402 5.2998 37.0801 13.9297 52.8604l-10 10c-9.44434 9.47461 -27.9678 17.1641 -41.3457 17.1641c-2.10254 0 -5.5 -0.22168 -7.58398 -0.494141 +c-30 -3.73047 -51 -31.7803 -51 -61.9307v-305.6c0 -8.83203 -7.16797 -16 -16 -16h-32c-8.83203 0 -16 7.16797 -16 16v303.15c0 67.9395 55.4902 129.35 123.44 128.85c27.7246 -0.138672 66.1006 -16.1992 85.6592 -35.8496l10 -10 +c15.8203 8.5498 33.6602 13.8496 52.9004 13.8496c25.1631 -0.000976562 60.1289 -14.3369 78.0498 -32l5.66992 5.66016c2.58691 2.58691 7.65625 4.6875 11.3154 4.6875s8.72754 -2.10059 11.3145 -4.6875z" /> +d="M32 64v48h448v-48c-0.0478516 -23.5742 -14.3848 -55.4229 -32 -71.0898v-40.9102c0 -8.83203 -7.16797 -16 -16 -16h-32c-8.83203 0 -16 7.16797 -16 16v16h-256v-16c0 -8.83203 -7.16797 -16 -16 -16h-32c-8.83203 0 -16 7.16797 -16 16v40.9102 +c-17.6152 15.667 -31.9521 47.5156 -32 71.0898zM496 192c8.83203 0 16 -7.16797 16 -16v-16c0 -8.83203 -7.16797 -16 -16 -16h-480c-8.83203 0 -16 7.16797 -16 16v16c0 8.83203 7.16797 16 16 16h16v186.75v0.00585938c0 38.2256 31.0244 69.25 69.25 69.25 +c15.835 0 37.7734 -9.08789 48.9697 -20.2861l19.2607 -19.2695c29.8994 13.1299 59.1094 7.60938 79.7295 -8.62012l0.169922 0.169922c2.58691 2.58496 7.65332 4.68262 11.3105 4.68262c3.65625 0 8.72266 -2.09766 11.3096 -4.68262l11.3096 -11.3096 +c2.58789 -2.58691 4.68848 -7.65625 4.68848 -11.3154s-2.10059 -8.72852 -4.68848 -11.3154l-105.369 -105.369c-2.58691 -2.58789 -7.65625 -4.68848 -11.3154 -4.68848s-8.72852 2.10059 -11.3154 4.68848l-11.3096 11.3096 +c-2.57617 2.58496 -4.66797 7.64551 -4.66797 11.2949s2.0918 8.70996 4.66797 11.2949l0.169922 0.169922c-16.2295 20.6201 -21.75 49.8506 -8.62012 79.7305l-19.2695 19.2598c-3.43652 3.42969 -10.165 6.21387 -15.0205 6.21387 +c-11.71 0 -21.2344 -9.50391 -21.2598 -21.2139v-186.75h416z" /> - + @@ -2068,6 +2074,14 @@ c22.3008 -10.2002 46.9004 -16 72.9004 -16s50.7002 5.7998 72.9004 16h55.0996z" /> d="M464 416c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-416c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h416zM380.4 125.5l-67.1006 66.5l67.1006 66.5c4.7998 4.7998 4.7998 12.5996 0 17.4004l-40.5 40.5 c-4.80078 4.7998 -12.6006 4.7998 -17.4004 0l-66.5 -67.1006l-66.5 67.1006c-4.7998 4.7998 -12.5996 4.7998 -17.4004 0l-40.5 -40.5c-4.7998 -4.80078 -4.7998 -12.6006 0 -17.4004l67.1006 -66.5l-67.1006 -66.5c-4.7998 -4.7998 -4.7998 -12.5996 0 -17.4004 l40.5 -40.5c4.80078 -4.7998 12.6006 -4.7998 17.4004 0l66.5 67.1006l66.5 -67.1006c4.7998 -4.7998 12.5996 -4.7998 17.4004 0l40.5 40.5c4.7998 4.80078 4.7998 12.6006 0 17.4004z" /> + + - + + +d="M32 224h32v-192h-32h-0.0380859c-17.6436 0 -31.9619 14.3184 -31.9619 31.9619v0.0380859v128v0.0380859c0 17.6436 14.3184 31.9619 31.9619 31.9619h0.0380859zM544 272v-272c-0.0351562 -35.293 -28.707 -63.9648 -64 -64h-320 +c-35.293 0.0351562 -63.9648 28.707 -64 64v272v0.0263672c0 44.1455 35.8281 79.9736 79.9736 79.9736h0.0263672h112v64c0 17.6641 14.3359 32 32 32s32 -14.3359 32 -32v-64h112h0.0263672c44.1455 0 79.9736 -35.8281 79.9736 -79.9736v-0.0263672zM264 192 +c0 22.0801 -17.9199 40 -40 40s-40 -17.9199 -40 -40s17.9199 -40 40 -40h0.00292969c22.0781 0 39.9971 17.9189 39.9971 39.9971v0.00292969zM256 64h-64v-32h64v32zM352 64h-64v-32h64v32zM456 192c0 22.0801 -17.9199 40 -40 40s-40 -17.9199 -40 -40 +s17.9199 -40 40 -40h0.00292969c22.0781 0 39.9971 17.9189 39.9971 39.9971v0.00292969zM448 64h-64v-32h64v32zM640 192v-128v-0.0380859c0 -17.6436 -14.3184 -31.9619 -31.9619 -31.9619h-0.0380859h-32v192h32h0.0380859c17.6436 0 31.9619 -14.3184 31.9619 -31.9619 +v-0.0380859z" /> - + - + +d="M223.999 224c17.6328 -0.03125 31.9727 -14.3672 32.0078 -32c0 -17.6641 -14.3359 -32 -32 -32s-32 14.3359 -32 32c0 17.6602 14.333 31.9961 31.9922 32zM438.171 320c16.3789 -29.375 15.0039 -73.125 -25.1309 -128c40.1348 -54.875 41.5098 -98.625 25.1309 -128 +c-29.1309 -52.375 -101.646 -43.625 -116.275 -41.875c-21.5039 -51.25 -54.2617 -86.125 -97.8965 -86.125s-76.3906 34.875 -97.8965 86.125c-14.627 -1.75 -87.1426 -10.5 -116.273 41.875c-16.3789 29.375 -15.0039 73.125 25.1289 128 +c-40.1328 54.875 -41.5078 98.625 -25.1289 128c10.877 19.5 40.5078 50.625 116.273 41.875c21.5059 51.25 54.2617 86.125 97.8965 86.125s76.3926 -34.875 97.8965 -86.125c75.7656 8.875 105.398 -22.375 116.275 -41.875zM63.3389 96 +c3.75195 -6.625 19.0059 -11.875 43.6348 -11c-2.75 13 -5.125 26.375 -6.75 40.125c-7.75195 6.25 -15.0039 12.625 -21.8809 19.125c-15.1289 -23.5 -19.0039 -41 -15.0039 -48.25zM100.224 258.875c1.625 13.5 3.875 26.875 6.75 40.25c-1.875 0 -4 0.375 -5.75 0.375 +c-21.5059 0 -34.5078 -5.375 -37.8848 -11.5c-4 -7.25 -0.125 -24.75 15.0039 -48.25c6.87695 6.5 14.1289 12.875 21.8809 19.125zM223.999 384c-9.50195 0 -22.2539 -13.5 -33.8828 -37.25c11.2539 -3.75 22.5059 -8 33.8828 -12.875 +c11.3789 4.875 22.6309 9.125 33.8828 12.875c-11.627 23.75 -24.3809 37.25 -33.8828 37.25zM223.999 0c9.50195 0 22.2559 13.5 33.8828 37.25c-11.252 3.75 -22.5039 8 -33.8828 12.875c-11.377 -4.875 -22.6289 -9.125 -33.8828 -12.875 +c11.6289 -23.75 24.3809 -37.25 33.8828 -37.25zM223.999 112c44.1602 0 80 35.8398 80 80s-35.8398 80 -80 80s-80 -35.8398 -80 -80s35.8398 -80 80 -80zM384.659 96c4 7.25 0.125 24.75 -15.0039 48.25c-6.875 -6.5 -14.127 -12.875 -21.8789 -19.125 +c-1.625 -13.75 -4 -27.125 -6.75195 -40.125c24.6309 -0.875 40.0098 4.375 43.6348 11zM369.655 239.75c15.1289 23.5 19.0039 41 15.0039 48.25c-3.375 6.125 -16.3789 11.5 -37.8828 11.5c-1.75 0 -3.87695 -0.375 -5.75195 -0.375 +c2.87695 -13.375 5.12695 -26.75 6.75195 -40.25c7.75195 -6.25 15.0039 -12.625 21.8789 -19.125z" /> + +d="M128 192c70.6562 0 128 -57.3438 128 -128s-57.3438 -128 -128 -128s-128 57.3438 -128 128s57.3438 128 128 128zM507 246.86c14.2402 -24.3799 -3.58008 -54.8604 -32.0898 -54.8604h-213.82c-28.5098 0 -46.3301 30.4805 -32.0898 54.8604l106.93 182.85 +c5.97266 10.0967 20.3398 18.291 32.0703 18.291s26.0977 -8.19434 32.0703 -18.291zM480 160c17.6641 0 32 -14.3359 32 -32v-160c0 -17.6641 -14.3359 -32 -32 -32h-160c-17.6641 0 -32 14.3359 -32 32v160c0 17.6641 14.3359 32 32 32h160z" /> +d="M464 320c26.4961 0 48 -21.5039 48 -48v-224c0 -26.4961 -21.5039 -48 -48 -48h-416c-26.4961 0 -48 21.5039 -48 48v288c0 26.4961 21.5039 48 48 48h160l64 -64h192zM359.5 152v16c0 8.83203 -7.16797 16 -16 16h-64v64c0 8.83203 -7.16797 16 -16 16h-16 +c-8.83203 0 -16 -7.16797 -16 -16v-64h-64c-8.83203 0 -16 -7.16797 -16 -16v-16c0 -8.83203 7.16797 -16 16 -16h64v-64c0 -8.83203 7.16797 -16 16 -16h16c8.83203 0 16 7.16797 16 16v64h64c8.83203 0 16 7.16797 16 16z" /> - - + +d="M438.406 70.4062c-3.20312 -12.8125 -3.20312 -57.6094 0 -73.6094c6.39062 -6.39062 9.58887 -12.792 9.59375 -19.2031v-16c0 -16 -12.7969 -25.5938 -25.5938 -25.5938h-326.406c-54.4062 0 -96 41.5938 -96 96v320c0 54.4062 41.5938 96 96 96h326.406 +c16 0 25.5938 -9.59375 25.5938 -25.5938v-332.812c0 -9.59375 -3.19824 -15.9893 -9.59375 -19.1875zM380.797 64h-284.797c-16 0 -32 -12.7969 -32 -32s12.7969 -32 32 -32h284.797v64zM128.016 271.984c0 -0.515625 0.140625 -0.984375 0.140625 -1.5l37.1094 -32.4688 +c1.50488 -1.31934 2.72656 -4.01465 2.72656 -6.01562c0 -4.41211 -3.58008 -7.99609 -7.99219 -8h-0.015625c-1.625 0.0820312 -3.97656 0.97168 -5.25 1.98438l-23.5938 20.6406c11.5469 -49.5781 55.7656 -86.625 108.859 -86.625s97.3125 37.0469 108.875 86.625 +l-23.5938 -20.6406c-1.25 -1.08691 -3.60938 -1.96875 -5.26562 -1.96875v0h-0.015625c-1.9502 0.108398 -4.64551 1.32617 -6.01562 2.71875c-1.01074 1.27832 -1.89941 3.6377 -1.98438 5.26562c0.107422 1.9541 1.33203 4.64941 2.73438 6.01562l37.1094 32.4688 +c0.015625 0.53125 0.15625 1 0.15625 1.51562c0 11.0469 -2.09375 21.5156 -5.0625 31.5938l-21.2656 -21.25c-1.29492 -1.2959 -3.83105 -2.34766 -5.66309 -2.34766c-4.41895 0 -8.00488 3.58594 -8.00488 8.00488c0 1.82812 1.04883 4.36133 2.33984 5.65527 +l26.4219 26.4062c-8.47949 17.6582 -29.249 39.7295 -46.3594 49.2656c5.2959 -8.46484 9.59375 -23.4395 9.59375 -33.4248c0 -16.7217 -10.5977 -38.7705 -23.6562 -49.2158c8.64258 -8.95605 15.6562 -26.3262 15.6562 -38.7725 +c0 -25.0283 -19.8799 -49.5117 -44.375 -54.6494l-1.42188 34.2812l12.6719 -8.625c0.557617 -0.379883 1.55762 -0.6875 2.23242 -0.6875h0.0175781h0.0253906c2.19727 0 3.98145 1.7832 3.98145 3.98047c0 0.609375 -0.254883 1.52832 -0.569336 2.05078l-8.53125 14.3125 +l17.9062 3.71875c1.75977 0.367188 3.1875 2.12402 3.1875 3.92188s-1.42773 3.55469 -3.1875 3.92188l-17.9062 3.71875l8.53125 14.3125c0.314453 0.522461 0.569336 1.44141 0.569336 2.05078c0 2.19727 -1.78418 3.98047 -3.98145 3.98047h-0.0253906 +c-0.668945 -0.0263672 -1.67676 -0.327148 -2.25 -0.671875l-14.1875 -9.65625l-4.6875 112.297c-0.0927734 2.11328 -1.88477 3.82812 -4 3.82812s-3.90723 -1.71484 -4 -3.82812l-4.625 -110.812l-12 8.15625c-0.561523 0.380859 -1.56836 0.69043 -2.24707 0.69043 +c-2.20996 0 -4.00293 -1.79297 -4.00293 -4.00293c0 -0.607422 0.251953 -1.52441 0.5625 -2.04688l8.53125 -14.3125l-17.9062 -3.71875c-1.75977 -0.364258 -3.1875 -2.11719 -3.1875 -3.91406s1.42773 -3.5498 3.1875 -3.91406l17.9062 -3.73438l-8.53125 -14.2969 +c-0.285156 -0.529297 -0.537109 -1.44629 -0.5625 -2.04688c0.0507812 -0.928711 0.611328 -2.23047 1.25 -2.90625c0.639648 -0.603516 1.87109 -1.09277 2.75 -1.09375c0.677734 0.00292969 1.68555 0.311523 2.25 0.6875l10.3594 7.04688l-1.35938 -32.7188 +c-24.4951 5.14746 -44.375 29.6396 -44.375 54.6699c0 12.4482 7.01367 29.8232 15.6562 38.7832c-13.0586 10.4434 -23.6562 32.4893 -23.6562 49.21c0 9.99316 4.30469 24.9775 9.60938 33.4463c-17.1104 -9.53906 -37.8867 -31.6104 -46.375 -49.2656l26.4219 -26.4219 +c1.28516 -1.29199 2.3291 -3.81934 2.3291 -5.64258c0 -4.41504 -3.58398 -7.99902 -7.99902 -7.99902c-1.82324 0 -4.35059 1.04395 -5.64258 2.3291l-21.2656 21.2656c-2.98438 -10.0938 -5.07812 -20.5625 -5.0625 -31.625z" /> d="M422.19 338.05c5.3291 -3.24023 5.2998 -11.2695 -0.0507812 -14.46l-198.14 -118.14l-198.13 118.14c-5.35059 3.19043 -5.37988 11.2305 -0.0605469 14.46l165.971 100.88c19.9102 12.1006 44.5195 12.1006 64.4297 0zM436.03 293.42 c5.33008 3.17969 11.9697 -0.839844 11.9697 -7.25v-197.7c0 -23.7598 -12.1104 -45.7393 -31.79 -57.7002l-152.16 -92.4795c-10.6602 -6.48047 -24.0498 1.5498 -24.0498 14.4297v223.82zM0 286.17c0 6.41016 6.63965 10.4297 11.9697 7.25l196.03 -116.88v-223.81 c0 -12.8906 -13.3799 -20.9102 -24.0498 -14.4307l-152.16 92.4697c-19.6797 11.9609 -31.79 33.9307 -31.79 57.7002v197.7z" /> - + +d="M511.328 427.197c-11.6074 -38.7021 -34.3076 -111.702 -61.3037 -187.701c6.99902 -2.09375 13.4043 -4 18.6074 -5.59277c6.28125 -1.91504 11.3789 -8.79785 11.3789 -15.3643c0 -2.21094 -0.842773 -5.58984 -1.88086 -7.54199 +c-22.1055 -42.2969 -82.6904 -152.795 -142.479 -214.403c-0.999023 -1.09375 -1.99902 -2.5 -2.99902 -3.5c-31.501 -31.5098 -93.2285 -57.083 -137.784 -57.083c-107.546 0 -194.83 87.2842 -194.83 194.831c0 44.5391 25.5566 106.25 57.0469 137.748 +c1 1 2.40625 2 3.49902 3c61.6006 59.9053 171.975 120.405 214.374 142.498c1.95215 1.03809 5.33008 1.88086 7.54102 1.88086c6.56641 0 13.4492 -5.09863 15.3613 -11.3809c1.59375 -5.09375 3.5 -11.5928 5.59277 -18.5928 +c75.8955 26.999 148.978 49.7021 187.675 61.2959c1.26465 0.382812 3.36426 0.692383 4.68555 0.692383c8.93262 0 16.1826 -7.25 16.1826 -16.1826c0 -1.29785 -0.298828 -3.35938 -0.667969 -4.60352zM319.951 127.998 +c-0.00976562 70.6348 -57.3457 127.962 -127.98 127.962c-70.6455 0 -127.98 -57.335 -127.98 -127.98c0 -70.6445 57.335 -127.979 127.98 -127.979h0.00488281c70.6426 0 127.976 57.333 127.976 127.976v0.0224609zM191.971 159.997 +c-0.00292969 -17.6582 -14.3359 -31.9902 -31.9951 -31.9902c-17.6611 0 -31.9951 14.334 -31.9951 31.9951s14.334 31.9951 31.9951 31.9951h0.0361328c17.6416 0 31.959 -14.3174 31.959 -31.959v-0.0410156zM223.966 79.998 +c-0.000976562 -8.8291 -7.16797 -15.9951 -15.998 -15.9951s-15.9971 7.16699 -15.9971 15.998c0 8.83008 7.16699 15.9971 15.9971 15.9971c8.80371 -0.0283203 15.9707 -7.19629 15.998 -16z" /> d="M96 -48c0 -8.7998 -7.2002 -16 -16 -16h-32c-8.7998 0 -16 7.2002 -16 16v480c0 8.7998 7.2002 16 16 16h32c8.7998 0 16 -7.2002 16 -16v-480zM224 -48c0 -8.7998 -7.2002 -16 -16 -16h-32c-8.7998 0 -16 7.2002 -16 16v480c0 8.7998 7.2002 16 16 16h32 c8.7998 0 16 -7.2002 16 -16v-480z" /> +d="M502.63 409c5.15625 -5.1709 9.33984 -15.293 9.33984 -22.5947c0 -7.31543 -4.19727 -17.4521 -9.37012 -22.625l-46.3301 -46.3203c-3.24707 -3.25684 -9.4248 -7.07812 -13.7891 -8.53027l-36.4805 -12.1602l-76.2402 -76.2393 +c8.79004 -12.2002 15.7705 -25.5605 19.1602 -40.2002c7.74023 -33.3896 0.870117 -66.8701 -22 -89.75c-7.87793 -7.8418 -22.877 -16.9141 -33.4795 -20.25c-18.54 -6.00977 -32.6709 -23.29 -34.4307 -42.1396c-2.29004 -23.8105 -11.4502 -45.8301 -28.4502 -62.71 +c-45.5596 -45.4805 -127.5 -37.3809 -182.979 18.0693c-55.4805 55.4502 -63.6904 137.45 -18.0498 182.96c16.8799 16.9902 38.9102 26.1699 62.6094 28.4404c18.9404 1.76953 36.1504 15.8994 42.1504 34.46c3.33105 10.6016 12.3984 25.5957 20.2402 33.4697 +c22.8799 22.8799 56.4297 29.7803 89.8799 22c14.5996 -3.39941 27.9395 -10.3799 40.0996 -19.1396l76.2598 76.2598l12.1602 36.5098c1.45215 4.36426 5.27344 10.542 8.53027 13.79l46.2803 46.3301c5.17383 5.1748 15.3115 9.375 22.6299 9.375 +c7.31738 0 17.4561 -4.2002 22.6299 -9.375zM208 96c26.4961 0 48 21.5039 48 48s-21.5039 48 -48 48s-48 -21.5039 -48 -48s21.5039 -48 48 -48z" /> @@ -4352,16 +4387,20 @@ c14.2998 -1.2002 26.5 -10.7002 29.7998 -24.2002zM336 448c8.7998 0 16 -7.2002 16 c0 -13.2998 -10.7002 -24 -24 -24h-8v-136c0 -13.2998 -10.7002 -24 -24 -24h-80c-13.2998 0 -24 10.7002 -24 24v136h-8c-13.2998 0 -24 10.7002 -24 24v136c0 25.0996 19.2998 45.5 43.9004 47.5996c15 -9.7998 32.8994 -15.5996 52.0996 -15.5996 s37.0996 5.7998 52.0996 15.5996z" /> +d="M502.609 137.958l-96.7041 -96.7168c-5.15039 -5.13184 -15.2324 -9.29785 -22.5029 -9.29785c-7.27148 0 -17.3535 4.16602 -22.5039 9.29785l-80.3262 80.418l-9.89258 -9.9082c9.41016 -20.7256 17.0469 -56.0186 17.0469 -78.7803 +c0 -26.3193 -10.0596 -66.5244 -22.4541 -89.7422c-4.50098 -8.50098 -16.3936 -9.59473 -23.207 -2.79785l-107.519 107.515l-17.7998 -17.7988c0.703125 -2.60938 1.60938 -5.00098 1.60938 -7.79785v-0.000976562c0 -17.667 -14.3379 -32.0059 -32.0049 -32.0059 +s-32.0059 14.3389 -32.0059 32.0059s14.3389 32.0049 32.0059 32.0049c2.79688 0 5.18848 -0.90625 7.79785 -1.60938l17.7998 17.7998l-107.518 107.515c-6.79883 6.8125 -5.7041 18.6113 2.79688 23.2061c23.2197 12.3936 63.4248 22.4531 89.7451 22.4531 +c22.7627 0 58.0576 -7.63672 78.7832 -17.0469l9.79883 9.79883l-80.3105 80.417c-5.13086 5.16602 -9.29395 15.2686 -9.29395 22.5498s4.16309 17.3838 9.29395 22.5498l96.7197 96.7168c5.11621 5.13281 15.1514 9.29785 22.3984 9.29785h0.105469h0.0449219 +c7.28223 0 17.3857 -4.16602 22.5527 -9.29785l80.3262 -80.3076l47.8047 47.8965c5.43262 5.42773 16.0742 9.83398 23.7539 9.83398s18.3213 -4.40625 23.7539 -9.83398l47.5088 -47.5059c5.42188 -5.43555 9.82129 -16.0771 9.82129 -23.7539 +s-4.39941 -18.3184 -9.82129 -23.7529l-47.8057 -47.8975l80.3105 -80.417c5.12305 -5.13672 9.28125 -15.1934 9.28125 -22.4482c0 -7.30469 -4.20703 -17.4111 -9.39062 -22.5576zM219.562 250.567l73.8252 73.8223l-68.918 68.8994l-73.8096 -73.8066zM457.305 160.461 +l-68.9023 68.916l-73.8242 -73.8232l68.918 -68.8994z" /> +d="M305.449 -14.5898c7.3916 -7.29785 6.18848 -20.0967 -3 -25.0039c-77.7129 -41.8027 -176.726 -29.9102 -242.344 35.708c-65.6016 65.6035 -77.5098 164.523 -35.6914 242.332c4.89062 9.09473 17.6895 10.2979 25.0029 3l116.812 -116.813l27.3945 27.3945 +c-0.6875 2.60938 -1.59375 5.00098 -1.59375 7.81348c0 17.666 14.3379 32.0039 32.0039 32.0039s32.0039 -14.3379 32.0039 -32.0039s-14.3379 -32.0039 -32.0039 -32.0039c-2.79785 0 -5.2041 0.890625 -7.79785 1.59375l-27.4102 -27.4102zM511.976 144.933 +c0.0136719 -0.248047 0.0253906 -0.650391 0.0253906 -0.899414c0 -8.84668 -7.18066 -16.0615 -16.0273 -16.1025h-32.1133c-8.27148 0.0244141 -15.3916 6.74512 -15.8926 15.002c-7.50098 129.519 -111.515 234.533 -240.937 241.534 +c-8.28125 0.441406 -15.0029 7.5293 -15.0029 15.8223c0 0.0234375 0 0.0625 0.000976562 0.0859375v31.5986c0.0361328 8.84766 7.24609 16.0273 16.0938 16.0273c0.250977 0 0.657227 -0.0107422 0.908203 -0.0253906c163.224 -8.59473 294.443 -139.816 302.944 -303.043 +zM415.964 145.229c0.0195312 -0.299805 0.0361328 -0.788086 0.0361328 -1.08887c0 -8.91309 -7.23438 -16.1758 -16.1475 -16.21h-32.208c-8.08594 0.0585938 -15.2061 6.64648 -15.8926 14.7051c-6.90625 77.0107 -68.1172 138.91 -144.924 145.224 +c-8.16602 0.585938 -14.7959 7.70605 -14.7988 15.8926v32.1143v0.00390625c0 8.90625 7.22754 16.1338 16.1338 16.1338c0.322266 0 0.84375 -0.0185547 1.16504 -0.0419922c110.123 -8.50098 198.229 -96.6074 206.636 -206.732z" /> + + +d="M319.41 128c71.4902 -3.09961 128.59 -61.5996 128.59 -133.79c0 -32.1318 -26.0781 -58.21 -58.21 -58.21h-331.58c-32.1318 0 -58.21 26.0781 -58.21 58.21c0 72.1904 57.0996 130.69 128.59 133.79l95.4102 -95.3896zM224 144c-70.6562 0 -128 57.3438 -128 128 +v110.18c0 12.2393 9.30078 25.6611 20.7598 29.96l84.7705 31.79c5.99707 2.24902 16.0645 4.07422 22.4697 4.07422s16.4727 -1.8252 22.4697 -4.07422l84.7705 -31.75c11.459 -4.29883 20.7598 -17.7217 20.7598 -29.9609v-0.0390625v-110.18 +c0 -70.6562 -57.3438 -128 -128 -128zM184 376.33v-16.6602c0 -2.75977 2.24023 -5 5 -5h21.6699v-21.6699c0 -2.75977 2.24023 -5 5 -5h16.6602c2.75977 0 5 2.24023 5 5v21.6699h21.6699c2.75977 0 5 2.24023 5 5v16.6602c0 2.75977 -2.24023 5 -5 5h-21.6699v21.6699 +c0 2.75977 -2.24023 5 -5 5h-16.6602c-2.75977 0 -5 -2.24023 -5 -5v-21.6699h-21.6699c-2.75977 0 -5 -2.24023 -5 -5zM144 288v-16c0 -44.1602 35.8398 -80 80 -80s80 35.8398 80 80v16h-160z" /> @@ -4645,5 +4697,242 @@ c2.57324 2.60352 7.63379 4.71777 11.2949 4.71777s8.72168 -2.11426 11.2949 -4.717 d="M496 320c79.4883 0 144 -64.5117 144 -144s-64.5117 -144 -144 -144h-352c-79.4844 0.00390625 -143.993 64.5156 -143.993 144c0 79.4883 64.5117 144 144 144s144 -64.5117 144 -144c0 -24.1113 -10.8711 -59.9512 -24.2666 -80h112.52 c-13.3955 20.0488 -24.2666 55.8887 -24.2666 80c0 79.4883 64.5117 144 144 144h0.00683594zM64 176c0 -44.1602 35.8398 -80 80 -80s80 35.8398 80 80s-35.8398 80 -80 80s-80 -35.8398 -80 -80zM496 96c44.1602 0 80 35.8398 80 80s-35.8398 80 -80 80 s-80 -35.8398 -80 -80s35.8398 -80 80 -80z" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/vendor/fontawesome-free/webfonts/fa-solid-900.ttf b/static/vendor/fontawesome-free/webfonts/fa-solid-900.ttf index 704a2a996eb5037908200da22c7c86f640034753..5b979039ab28aaae305074541fe39258753ba624 100644 GIT binary patch delta 19852 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 Hd+PoV?kV1` delta 9688 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 diff --git a/static/vendor/fontawesome-free/webfonts/fa-solid-900.woff b/static/vendor/fontawesome-free/webfonts/fa-solid-900.woff index 7adb9d59142b90543e6e961bbb86df503caee8ed..beec79178427adba9f767e1c1d2bd205044a85da 100644 GIT binary patch delta 102606 zcmW(*V_4_U+s(G^Hf*+S*Jism+x5xD);HU(&9>X@jhk(|=l6eJoS8Z2I`>?2zZq|T zK!=vW;CU)bNr8cbfq{X8Lcx&#t08f{z~v#367fg@!v82yd^w-0w1lM8e-4HJILZHm z9`aoI2Q%A$n%#f=f4Z@Fu;z-Hy@}I*S{oP`%zr>y#MdLH%fJ3lnoNaLecaJe>c53y%iS+_nBs1p~KY`A;DG4^&X% zFyE~0-Tn)-Qu%MF|G>adQ=3&FG3*@8{`1-X=R+X=Ps@h-lNxAm;^hPe?*5+-jP!r; zP%_{@98Byj{?nYmz#ve-z@WcV*H92RIl8)mfqMr2mn$R~7|VHNL3*}VwwbXpU~CQ+ zoV8jhEP7w#Z9t%+OA03U3#RwK1pn&}=|AB72Z;X|B;>(=HPrvJ|BC+wo4ikL4D|i! z>pLDyAL;AcMk(oKlVNLOVPRpdV`Ddl2LD-0hlEUu7m&(I@(cuuCfp#2!>OdBQ;k{= zw_(Z9v9LgZ0j|!-$6nbN%f2e98YF!;u%Y_@Twl5^iaEmvG;Q@V7*kS9*>-h13Z**D z(5-D?4FBj$Q?f$J4!UTGQQu)uz!)0eBvFJ?1jQnFts_H2VKFnBWV`0xW#4sqndEJ4 z{d8Ni|JuyC;df=~<^2lywqZ25W4sP&7HOA!PR1A}7RTD#Q{sZE9M)3p4TiKT;z)uq;RotSDh)H@Z(WNOidCxpKo6Kxq zf>%cg)VNQ?tMzS6l1+yxg><}kEQL+n;g`2d8bi{XMFv9}=WTLznqtH7sbz`ISq;PY z(KVBc7K9p%ssTQmC2g7--KuGuy3A_VW$3ephn$CQcLQkJ*3;Tcjc$#|)88=V{q*{h zKzX7~GC|cym2f3Iy{4k(!XgrZ>wYBvoJo{vSZYTQJ)gSnw1j;Ag82vcHOOKt0=}TifPHBLyUBe^R-Q=;_RM! zvvj?AHg);6a&zfh%X-g5&*HQGeT8AFRkJoa@COhZeT+@ksQBxkpOpmgPp2f9hK!zRW zcjJ~xZkn)Ihq zd=f=Nf*qxvEYmo7mMqt*M8lX=Lr^uP?KnY0w4d1g7(zp~pF(P)dP8#YuVJbT28yf1 zFsj5!N@sI~3>nigpo%I}BC*~a3188W%%qt41=%}NKqf#@Ocw4$+y`XrB77q0ljJLd z)}G}?w%5CB!Gq1*f3*Y61Kt~Qw3FcllN-MWI1>dxDi8nOiQ>VL-b=fQ z@e&}3k=}+Az(VbZy!p{49Do(Ez4aX6M~X7wpujq?Sr^2Y2 zW4iZ8rU6`LTgxOQ??Mu`EkQ0UD z8ej0B(vZi?_FY4eQ^0#G8E(b&!ZNc$S@r@N?lbq3wdQZ5PjAIJrtuh-Ag3t5wOtbq@gyN5Iez(0{N?+yHaRBd)S*XMvFTbGX@R1xjY#|Ic{D*gm zMk2<(mw&!8FVEaSyh7oIiQssbaOh1vv)D{g-rqL~e72>|laQvBNgMs}$iB6m?OyU7 z$(wH~8@Z~$S&H6MS3d?_zoxw~9y>FMzM5O6(|ET#dlQ(ncOiDV$P0lC{3j>_fd`8Bw8s~;gdhsR>;}R^6o6B_kW%w>>Inqt zBN76i!2u$~Zaz`Zk$3F0p)cBbs(HnK3XcRY1pB?K--OWG*Y0x9duS#Uw^*c^LNrF3 zVSN1Rde4JCo4T#G@@T+?&tZ{0xBc_KAhx(YJ9qBfU`jqnZv2GjCnc_iT{pL0)LUhb z6Ai`rFLFr*=M`_YnNxF!iGm+{Q(W_xQf`5BrUsot9A{(skj*Fd-WX^2(c~X9-&wD> zr-4!D2M|xZG!{l#Y+&mhH^R7;e-2{N*{r=j`mM_cCBy=;U+Q@~Wc$~1TjfY|vd?>4 z=KLc``Z#}j z9|;cN)*>$MiMem{c73|vLoAvkugfWGrtb_7V7W~T6h_$UkCdXh?mlE#nziU>h;_~i z417$Gg-S8QLN^F4`b<0)w8LB!8RdIQeYROg7{!Ak_oilu(g_4+{?^&WP@}(i< zP7=V?&{((SlX&e}U%5-*oZ2Cze&#ddu&bwWL*O0USiNhp3vH!1oa1_b1`x$vRnPVz zAnsd(Z7rB>>HL?P9suV2Xbl>rnK-Zjg#Bt60}< zI%e0}R1H3 zS^V?LIi>2;-QpMt9PzqZfMF*P1&MvR8XFsi?EH$F>7cEm0!|EQfeO`n;g#<^r_ZcdK*x)}SRWbFUQFT9rs_ma35-8@^9$G3% zS&UB_iBDzGWB6h6bJ0qd)a&*0Ew6`^ z80WyAK0Fcm3f)3dB4JZ`mlV90!y|n0QM?5l+I8R+5CeY$OZ`5(KqN)bN>=Au%4T%=KPB)N~CeezQV3A7RHa-_5A+$ z5KLvhD}HC8DAEqtx0E50J`Z*lWVj@WFE%lE_WVijIiwHV${ zQ>{3&n$`tb0L#JjSt6G3=oBo*g)1zp?`Frjp^EE-WFFd+a`SnTDM{k-v8MFtohh3` zT0HZ$&TbsFO82-t%5s~fDiQh%6oX0tz-k@c(u>+LASp<&Ele30@g(Vhrx*X|AZzc1m_%0+m;_djt^8`5l zeJ(E^C%5n`*)%kH#x?|^rDsX`X^FO4pB7Iqv4}X>HhJ_EJKP5|xu9Fx)$AsLnn?$6 z(pUHlA7ymdO*~;WUa#Gu{z*e=Eb4l)$iw6P1) z*)o#QGIZl&wCHojTHPn$o{Rp_Np%sRSysY56mdq`p!-Ec^%M=PMt`Rk)&%QKC^h?z zcJUf`K2_%&#``KExVj|CV|f(-cd%+vaW>F?RJ@K;hUfN73&aOloH>KS6CcjBRvd_N zLdMOs@`Xb)34(A8xXQ%7&!u>ZeCO-^hrA?h$odOy1PpeY16L^=VRFJ?gPe!ucB?ek zDofyKO3-_I6=9pB{_c5>J;#C#8qL@yW4a8ncKz!~3@yY2;FVGACI1T$^T{p{Bejv1 z(A@0=5)P5d%D2svZT%%P1~c|RZU{7%7uY+>bH>o>KBz3ICpJ4`n2>Itny%F zvK$?0h%v9FcOwx%5rlHZR`M7l#Qa;R-N*Wc6uRTs!SaX_%bOe)zw{W^c=%{QN%P~b zvD6YgTSo>=f$R`9TcQ_88k{>$pggsn6O=NUY?Tbh@D6QY&0XOrk~u-Ovul|(NN)NT z`>KMqVk=LIB^XzV)&(5vq5g~>zsfDHvp3}f)}TgwP$Z|6mvo%1sa_!mtLVjWhc=EQ*<74>{L zawIHjRuxa8Y%y2BPa+2I3b|}l9^87+p=}(Pa+S|Y(K}7ypJDH_BMB6bgfI>o>KB#Y z+u-Rj#-1C9@Ad^eY2%qHUrrX687s^Am&R3>!a*X<5d%Bqdf1%O_}vb5jFA2hmw)uO z?oy)}bmFo8d8=Byjd$&9&tp#fR``=txYoeR#>&ZRI_4pf1e#JLP;xd%|EdDRHw%m! z%troR0O!v#?aZ$#s2R0HSmRUu*9Ob1ynlciPP`6I^tKn!4*Ity=WBtSjpD4r)S7L} z-0I5v%r9-acES1FFMnz@&iMO4)1{rr%Ke0)|8}7cM~Ar7#PMf;MQhAni^OT~;#Ya4 z)Dk);v0~M}5q)NS5@5rjNKt5A(s(=Z9{d&%Dxb7fG!d7N`YC&H0pK4I#=kHwyoc+A zjruPslx70nw}eVC5E_x8qwM$?tHTwVT>{c?75;X-Z)D|Af90Vq*Evji5MPA@;G4Q= ztXbcYqFxo~A87PsumiSnkALlw=9heON%(#9SACULBc9eH{3Ou{Mb8M)?Grj|6U<)w zcVF`N(2c%xhHZ8Yr*IF8R_XR_U_l^w3X8MYK&f8_OW1!TWG*?eCE`>uk&3J!WsnU8|(l z<~G4QXF;uh&yX{*!O23UgfwktpG_6+cRrfk;AheK7j+O<15=XcJ>XW!-R10miY)jGgK50jq{A@ZEcPVcj4CYy^Nl-Jn7XO>&NQ?SP6ziD&cIn+JL2 z@?!b*<59fg5$%{#jz{N_G3fZUGTJS<;-~S|8N6SYSuGsX0 z={~5O>K|o@q}cMJPF-fZUnfy9njGn`>@@>>j@|8z1;q8p7uNT!A#-KcYA)MqYgqlx zaE7kSRRac5mws{^2TMrZHg)tU6~ZM{+GRdV!WQ08;nRS_mJOU2hs|0zQ(On9j64xs z=f|}vF>~&Vmg8HT6I~i+&u0&#d$w-NC+k9SnCR2Yd=-lUl(XABEiC1l94Qr5YB` z!2VlUS{BL+p;fFEf09_-+W7CPAP_i@>j9dN1UsFdsHSc5A3_aJ64Fr6_IX6&B0AjM zbPxJ7QL~*M`g3iNi+i1Ql2YsU^1DMo7kRHaz;U;51Cs9~+c~Rk2OBy`wfkCB+K zxh}3;5RlQEwdN){1TWhh z=GMC(3NOkGB&wY?Fcq}}!-y@CArbWA9QoHNwQN!BkNTBcn_!H@O6fD*rd%y&l9wCY z5Y39#GwR> z#|Lw1iFsM7oFvF;B-Ag02tcTNyb{64AZa+m6{V6D^6kg$demJ&$Q-Bj7GDM@V+(7d z!gtYL6Fgh0iI%5#1IDVF*Nhq%$SnasB*FOHU;%iCNa~D7pf`p@mWr?HGS%liZ>sbq zHe)(}$!`AMWSVrK=a$t+$;m;>E~q9|ErDOWXnHnGjA&mlik*n6W$C?tj3*nFztPoo zkRDZScGhME_8-b4>Y$sBty5n@0NbqS*X9}KB8nRtGEywf_K)P`=y8&09gpOHtdq}4 z0%pP zH?BtiVEjg*3t8$p%KnWbF<$v zK0Ylopr3k71CPYZ`wXH89Z@ClSMHA-pKd+xLTSOf z5p^2hFC`G{7?_`5y3$xdGkU#pF?C^oc_~l_t;HKXMLoq(y&gLKO+-~IqdRa9?BMi*T($^n^^{pCBDVaR@^hP%a?@cmimGkO&ElC5%3I zazMbw+Y}LWM@SEz4k95U0EBwSr5UcJZYu<5%L=v;(8|!H6j@_*WgcIzeoT~Eq0@U_ zMp#*OpFbGb=`edPTd`k@Sf`q~P}hB~7=NI?oc&9qB43x)njV>=80s{?SQw5s_=|8U zHmL4)7~yF`Ri_B%`!+zVF13TwF+3~yChb%@$7ZK2GEC!kF1Bf353q&x@M%jNMIAKBB7A1R0FJRvW+Y z7Gj#uCx_)5@gZ+iwtzuhT0g9l@%qO$6Z&yVOiaWgS!~8I@l5463#)eMxAA zyqpvGr({dP+aQ|b1t)*QGMd{Tqwx_TUIW6cKRQQQP(XW}RvSeb+q`yNGuNhH&4gi0 z_YH*ZxjsI{`oCq$Rq3>88l))w3uRe|Y8T9!=O6QDUP|1j-GMWO!D0f5ze&RswQpQI z{>HytKkM>>EigmlqQMbt)9{w5NAE*fG{+MIMo(T^*KG`r?}x!aw8%Zfj~E(RN*MD;rE=N$z6 zF#PIwy6{eO%(^@c)%9KYICK@P2l<;&7K%=6pto6|7|KUjCER=Qfpa8BftzHw-fox967_XOw4g zt500r7?W}E7;+}~DMLbT=PaOB=(27E-W7L~b7I1j_T8y)Fls3ix4UDTEz4{Q=cMWU z&4~M{(hV3)O@cR2bG?}O=NFiK=lgq4nUPV^DD{`!eOQ?MzfWq7TzW+X{Ek!((}lZc z2;GClU&JR!Ip06YiWS zGkOl`Qqh@(Van0Vxg`gK5oxd&AGblSetk1 zmg{+UP#Wj)T?fG574pr8M9r=y6p3?BJN)m#)TaE+HJ>NCjw54Q-IJ_hCL{NRd{4H< zFedPuBAXY%q`hgaO{`K5N>pE0kj>GC#Z`qLE0dV9`*ep@lx0&3F@>u`6NgAPameVs z;7BKmOP4j1LOq6<1NADBvnE79L^p-E;#;iE3Zv-55An1#51dv}A38e3YJXKMg`trT)!q#5a0!ErV4(y?dc*}lN76f{vR zK9l8I%HOt_EK!pxQWcmIU}9Wf@DML;g;(@Uv5HjRdV}S!Tr!zn;r`dl>s0)x0TFR^ z-H5RQG5U`qFDnze2*H6%WZ?Y#a}*<-Y(rbV+;1s;i_+V4jDZ9P5b{B)9+vplV>ysN z7JQ4>q;Yb~tMUzon7~C5A=Bo-W7r>TXOiBwkbB(xIh==_*U}|RT zlZxB`zflNSF4`<+Wly%vK zv`Zz>Xa-XR&%hXFR)3vDh8P}-Owa}^k9oSAv)=m8TVzd4o3;_ucrE=*3#R9?4&JgM zy=M30ZxYe+d2DIm`Bx+QrWC->Ejj^vYwEYWj3D)Rb;v+H?mSOai{r$`_bi;873~b2hAwhjO*yJn9Qj@%V zZ~D$(bR+L^-TMKZ|~mBMjAls{JS zX!1WvFdT%j*WEz@&t2moIpc0~8x4B!SU@kQr_1AdNQa&)v^s<)v`H;&Nv8Czgy8eS zt<>Y?9zF^{AVL4geaNc;QUJnNGoBxhPQ4?WGat}T_Jz(Ti0fMUOcpMI9KLE#f;ztG z)ehO}*!k2(=Diqsuw!6gblhY^sJRGUr4)3Tb|*4ei4NlgP*dgK=u+MLTPv(}ko$ov3G(12KiVWkbL0|z&4-;K6{_R~ zCo1NNm-51c*kcv8s;YyANDJ_!`c~){MRZof>fs8+?OA3c>`(=bJ{V0!%4%tQM!Qgr z)xJUkoX|S@Kjb=Lv{Uh?CL&%tarv18T|cpipZ&iTu2$G+#|+&ELxhASR$$$0OCV~^ zx8MNd$b)Zg)YUqyHTmJ&-BjOAMLiid>8;56ohS{zkCec<3-hWZ{;Dt)ztjxvf`7pd z;`YZTck_T?X6Vaoi3+8}4a7C}gO}ql8$9+f6C4UEKL*=y8jgQ`#sa4b)o$~9DiRJ_ zFi0*PIpbfW!Bo8q{|atlDhzvzrv`!PM=Aj(O+8N4u15Xn&E^ff{r@~NYNeR}saZXH zczsOJ!X6=+s7ZAA)cIi^_*DMx)M#4P*{JH6{-Bv`Kgj1MJ-WLnLO)MbGd-@Tu}!URT)wHQkguunGHEV z_d=P-5;6j|^4lv`+1+JQmhk3p<+)3(G8I32gfKAhKY>ESuQMxOaF**J{X{1tUV3O; z?gKV-hC}cb#+eD{W6=&zbi4WS+e-!i@L8;0Jr#|x(JoD$zrFTv`(CsHJ>-XgJR6Ri zfNx_Il!Oi%^`XjSChfGcRX?QxJmmrk<^Pvv7JO$ggcvYVFqbA3^ zV!nGYDrc;%5C`IK_pS|DTn6{O=hJt#E$P3`f27u1yj9KfE9n2QyYDRQUiRJ+`uUyH z?o!66(OxPcJ6f*CFMCuzdu>b+XtF8~RwRFbA!~F#tyRYFME@P7 zLzNaC5l+nj_hzzkEG)_*6-4$&71RCJTe~+jO?s6Eu-cbCXw&_T)BdCC04f*pTBmB)d~2ofT;;hu+|}P z;pl^Ii;>n0m#IwWwZL9%v$;Uw+&5^f7%=sw>D;h^gvW|dOaYd?Jux7HM4GgS??I1e zI*rX;u<{wJ(n}9py&BNC5#lq z7)}kLcbJTx;k-5w)XY0CfQSI=@WpZ#Q@bI9^BQ172{a`!Z{sYm5J(wlru1rlY#H1C zc6ZTy?x>xgbKl{!NZ%0MBx&@G@DtV$>@YGcoy2=wY33Dq>VtPk-IF3xc;K$6#GB*~ zUsR`UnT58ayMQf)4X^3J%W{74m~L=;7#m28O%HZG|5ZfwBG#b}$o&@0uGG`y2c9{9 z^S%Sj(E%bcd)>$r1kBpQGG{o$}Q6iH59;8^##y0VkfVKoYe3Y)d2R)ONOWTR-{dDqsU^hy&Y>= z+5G`Z2!x6Rln61<Cf%NXCHLTz1IM9yMj?_&HSmDiUZ(X>~|2HVd&mZV1KTchq%96JMC1`Yb3i$ z!hP%)=A%$>mn!F0nkzOVo`G>tMFXBj65-k)X-|T6bb_*2IS7Xe>PX6Su;NtEO#s!2g)DZ`szV9NnY~O`Se%wr^HOG{OmGE(+5d7#{h~|HWpyg#l3e z#8qxF!S$j!(9F;)J_)^+-l=Ay96XW;y;Ci<>vmg?@ZDEt*q%lRC4~SlFMj-bVBBAt zGA`&*0HSO^Yxm9X8;n;>rolM0vAA=MKyCmRjy3+cDN&q!QVD)~#s8)Kw760Jz;Qh; z>ElAhjn;n-4yQQgzB{5=#R_)8$NONce^Q0x-oA6OE|1YbDA_|@h>Ccv%7_X+=ZPf@ zH~#Do12DPZxHZ_F)Y)HTb1Pby&kVzYN9W7#3;Fv_vN-i0F?wi-i7IB9Bsxj(Iu5|W zN)Z_Y>AZq=I*j&8UIOoafoOO7^^3Z4w~!i+bBvxL`o>^)lyWsg_6udU5j6FD&IRx7ipW}DH z-Y;kTku-d9?&X?a_1XPj=C18@&z$1#r=Q`I70#;6S&Ba8z*2NyG@y^KJGGt{wN zqfq#?qX|Mh;RfW(WiDfy)x>e@F=ZP4dI5C*IIqWPkSz{Qtq$$-Bg@OKASY+9@K=tV zhA!OdfjYl?t%7g?@HSDQKjtxd+tDo|)ES^`A1P8U-Yl4k-Oz%4fo6X3+id=bnu7Xr zZdS*L-6iGZst1b3@mFpFw+`UQZo1Od`y*+;p2t_TM;i9qw@0O}gUTqj7IZaeqUZu` zJ83&*hHtW9;-gE?Q@B1Ai8JWAeux6KK8RH6O>T0-mUg}jC?_66RI2KQQVNaJhR@A% zjo0S1$88pVudtGER#`PV3DC;C6)1TGnR28yZqw&H8$|pqEY7M7tm;6{o8Hgj40|HJ zF0b10qpYa#I=P{+SkW0cWs9J2xCY{p747m7c(J)1kNxt~8mzO^%)apZnc$xnoky5D@@{=YrqZ4dpa9+KwOi zke*y|ro8@*n)z3STo|)eH@rd_Zwzy6LQ#|>PW-{$3-ieO4PJEb3R&SVD|L;2BczBa zo9EwZ55xOK9FKG3tr9f<_+E0v3Y_dedpjhG*+27ZN0C27Wq$ju5+Ftl&J4uL<#5Kf zIb`SDk0j30L@kd-?AOtGzF!LBpJhpyN!2co_ zL)a@edB<>BhFF?15`Xd*Aas~D1tGICQGO1)C(hz$KK2B@sZHB>e;jS;@WYgiNp37{ z>W**1PD2Yim3!V9TQ{6%ea&U--Dq1~vc>nX^&Yv2p z43eS<%+!%q%-d4G^2!G;!`p%tMxY^{W~5Kn~vbV zn9grF=apI0^dh16#_Fo-z>_5iFOQ6ltv5AWU!>q0UsLmW1)3&5hj$dq-S*G3ERt*& zd5}QUCXb0=6awIM&FfNd3ut4H}y<8GYnlO=-zUdO$u+wpOvk=1(cV6M~VQX)I z$JH*q-@6I1*i<2o#xquXaLt+Ad6oBAM8v7+XRz=K5o)%28CBUOU8cM`a`{ns!kPqMT4!u%W2UE6AgF+Z`S9^3a( zn$!~+G{6jTe^kvfn}EvQffxHPk+2n!@bEQjy8id%fiRLIegx6SD`uI*6sT8Uf7;wQJwF%n})tAW_}Nt<9|v5Q}uyje^&sq-_KWacMS zRKhDtY2`UImvT+m@jN-s8~!g>CPNV%4~DBrSAYiR5OqId57l0bi35I*Y6X*ePlQ3f zU)jQ&b^8|~o7VEIMpL#7s;JK0kAltq1i?>2zdPS^!qVeGGK?>fK|deN4ND6Q*P~@I z-uU2eU~90NH2W@+a)f&Mg^)&uZelom}wLBr_Uu(W{G=#c9R3p8H76RF1+2@Kr!ef zPv}%l#v}}s&>_S?;>@44K>4ZH0PJI zBM!CweU==CtMhxbnjcStaCiS z#%O-ZAmz=%`cZ(iT3iy+AcXpnW{kvrD~N5_%Ie7$WJN-qqq@1jk;GS)M$MQmu!L1` z8;eWvLgjzp!^Icq4;%}aNyO1$0g%GXwn;U;H4|k91s*hn8$y?3QpAs%)nhU^gs5#D z%KfxlFr4dmkS6qS%AQrjnS`H>^fz;Dr+HzUYr6LJZzPQF-4mbe=FCeiVVfSz8yi6yy!Fq1+90WTpT9lx56d!t5Dwx(!p(=+36{dPe-N=1wl$zT{*ELQKq(YnwKJ!#TA=5)=7nyZ&-= z^3PJNWd7?@OjumY5nH&W5i1fho24HLgPDlC(@@`d=Npxn-gLKgur|Q;STVhuX|)Jg zrrA}vF~*UM1}JDsS;lKmDu~oBMTZ-0KOrtsg>4O!8~A%I%GR1Av-19o7KP`o!URJ7 ziA^6w5Ww!#Y^=gAzb z945D5EqVyd#jfM#T$H!bVd#hAX5yG?3EY1QXxgAD&=Y8PgSuDcbvzYI!6kW;62GC-1ci5SILQnq8lqGoUF!Ma8YZzpDoIs#4lb6eD!|aT?_3 zP+Oku{H<-s6J{gLDp|;^>DaIsB^}p22b+B#s#*e6kZ)l8eu|>pW>vbb(|b+`K$$B_ zkP8(DIGw3d3Z|^_2Iw_q(=%33p)y(b>W5_*aj75(T6lA~tp4{Er z(l8QQ%|D?ID0igZIoBtSbw?5)}MYf30o;SkjxVaWm$q`IVvfw~-UUbSX7E zW=}>6#N#&fu?f=X)Aop&97XaF-%YiX)#Q9@U$ZR-s;j08HsripHgv|RSzIF2|40pQ za)L@ds534;Rv-eE%?VrB>z8u31x`L*V^z+h{toJFV0U=R`sLj2_+I%!yu`_MS7|Hs z!qXE2Ma@cCf4ha{hF#+^_MQ~XKV5s!{C&A-6MmPNODm1D`6}?l2N|2`7B?~fI1`9< zVK7)v+ZFcvtNmEaTz&_-9KD|#{^{4XgLXeSmzKcW#FVc{3}k2zXR?(oZXXE>-OoG6 zUcn0eILy5=ArgFNk>K=^8}dmeJozcHIMdkyPz~&yHN15AO$D}THr8hs$+LLISBp+i z{K06lz~Ow8teQ$D@pDA+j%jM_SUnYBPtWyfCsXvv?6((SO+6HWK8vGZ(%e>cR3``| z>;KZ+4Eqb?f3<8N)Y{dhCf`*{5VL8r^oZqFvh1*e)m>L+xrrX5Vf5c}7&B~^`XDq2 zJP#%H%wQz5O4dD|$mg-dKIwpON)JS37uGd9zOXM#$ArqHJ!)4kCF9>^WQ3tS&sDs; z(R;KjS5V)2+FM)5e^6{H{Wi}!y>Y3z&`Qo@wv1|*;SP(0QmyoWYl@QDN|E<9 z=raa{U>Q@`Q?`!r3&diG%Wv=2?^|iJTewxQF(HCbBjImWeA3hG;x1VV;ZxyT6KA}9 z(^(0(BRd=B$H#&g(gjA9f(AuE5c--qt%`&AR_+DZSk=_+T2ln~$>c$t&cpsyc!j&v zmlSxgZ=Jisl*MZqMf5(Uz~;jWgAa)>5nYU~z0{1`nwwRSN?h=)Wn7N26U08>QAZ0! zdQF+Y0TPN@ilAl(GIjlj0-41?kgk$@o4U^M*I}qfrs};!gxO*gmxv%h-vTBir=z`O zmdJ9hZ&k5bHT{GiMkBL_>?p-~>H-GoJ3mU7kkC?QyHyFg^YUiseH01U%);wQh zmEhK=?F6e46u+$hYs}n4_)YIE%#f{PB^t!r(s#)um%=?#J?Avy9k7F!@|(VFjnE9{ zER+jtoicibdztP!X|V#(dQPIL4c}HB17;)QK1M~O!d-UeuTIvS-nU^J!Qc7OKx+TATF8};Tj_5%@gMp3XyRE>+6pYH7hENOrLd-4zUz?MkN#X zbR*_~c^{t8ka@nguN366Buug6=3%w;VY=8iu!Eb7HyuOi<*V9&O{ zbxqnyT0qo&$zAbIO|F|)sVnl}pH$}S<7|I;X7D}Vc&A&S`&bAROz5bO_(>cve#3(L z!4JbC^4SO_-qbi2r&#a@`igLSlc(K2Unqo-EuK?;DCK*c3$>;tYmN3o-0-FpkpN@l zPY$4ZI&|miiou7!zY5g74nHY@M@|Q9n^!(2gxR1QsL|m}jQF)%x0bRkb@f!~Go-kf z{T#YOH(>_edk!~ERU)4@O^rJ&Ww@ii>S#lsBDUPgD47488g8vt$-}DH0TLU`#yI*| zm~X@{fye)R8^taXi%+zpq+G z%vtS5=UwHun2pNy7r`|<9?o9EV`_Dd~=;1 zN}5L%gQM$CpH3jr-%>)ZvHx<`%8UI|h$(yju~DL)hl9EBQ=OD4#;-HGd?{TlH%g_W zEwW^K8B4jy57DY8TV;{EsU=At3SnBGVnHj$+DB4tmJLc0@I|Rsqv@4+K%N)Uyaa+}y3Sqo0pjvI~HR2O_JBcNN^f~uwh1gmuSAs=`nu$-) za^dDj@e`HTWL0wnTXd@#&};6fiC(UCIBF@*NoNB6EE-1qDULciaHB95F9I|OPyrf8 z+)}Oa@eUUQFGN9iQAqSFl~1nCFj!3Q9X#D;w{1>p`Vbk5)hQW+xeA!vGP)2zUtW;FOCy zS9p@gKZ?foa%uPGplyv|07%$01eFTPGoQz3maMo0<1J@2A!7^HgQ@N^s#ck#M3?R} zcPD37ZWhVN%O^Ehf!gw;ZY{85#Nx-A8GdXw{3uHwSta(|?sxi?P(Sd006{>$zuK06 zk+uo{zqf6(mJ#Wk7ik(hFPXf3ikz2BA}rq(h9L)L30l!Y|K~kt45RZQ$6PjlO~jd1 zbJgtp@jXko@BPesh*h&?h=FK`t!{bq(jH$gnd0S#$Ug0fzR{Hg=S#ul>4xX6EyA&Y zrH6>Iy_y?`*oMcOlgX=jeiy1?xjxh6aWriVrJOC1NEC^JpyhKT!%1_bn2roZ90y=J zJK8>#t7KBekyvqTtf1==dsLl&`KUbE_3P#w+q#UIkgAI?=%6DcXB(^@ze2dh3S4^OADITN)sEtE7=^mY1p*JO1LzF9Rb zCy`&jjckrGP%UXUng9hiE(o8=K)-Oj;ub z_cBh+W69JYiZePtr*OS!Oc=(9lOgB}`B5{)j2}w842hSRoA?`0Yt7PE3fj^dCi33gO}5wkn=d^S^1wrTgW zoD#Kf8F`5{*sO0m1)hB_2O`P%VgT(quyD}_9f)&o-*rh zF+%B=i_DOYXQ+=|dB?;sh#e0%-mUFV@!lPle!T>zQ(RA}brmV@NvzAl7SW6-Co9Jn z>pAT-!va5@pIJPAUO5>`N?Qa|JLy8&x|UYsvW{ix25jJ+doce|h6@IDu`)e9w^CQ@ zE1>4)ske0=BIG?wQEt4E2$b6Rw5Qp2#XDK}rkmLNcVcHS|DHWwU&AY)(&mvXZr+f+ zhi$)v{C)j3yPIu5eN8!EE?1qeU~Vw~fK6YoTLP+To{HLkCI$E=3pe&$cTV#Q0IN~{ zd#m8#9Wpab>)*mlp;4Fa(1ATHMs3^nz98iGif=qWq{z}({sT^zLU~vgW;i%^j)ydA0CQvC9N6pp5@;jTm7h&V^_`n^^e?(0m?! z_?BB9zUA#yOyee|6~ty?=ZCkDSKpvQ7_XxWKa}3AaCgBxQM_`v5jIn{@%^Mgv`B%v zJ1NxH`7`?X0h2#RAAdz5l)vrgr%k&vO==l{i~}_mK&7brV)`@{7@zrR5lgRorG)hz zM@0Qy5cq?)K;WhaM14>ol94X4^vc&sSS;$|ksUgg-uzh>c;F_zZ_UL5d&WFW6IgU~ ze)#vv-Xg=lvqmD}n~r{~U>Jt*8f==+m}Wl@GrfrvXTYv_Uw@l?>*D=&f5Edd-f!b# zc;hXal|h@|X97IV9}Zal?z;o?`p0POL;Go(le(`H=#`ZroXEk_gfJra+frd8Vm7J3 z3Z*K^!GgbVS{AUd^|UCqq!U?!ViBL{;{IDWr%rE$WHKb16V)tWaiY``A(hjGv3H|C z0lcS8?Lfim1%EIxV87T`3dFNi+_Kew$ppLq3)5RB__;Is(B!UK%n%)Qcsw(?|Dx$3 z{fx2(TasiLvgUXG8&yfk#zd)(+9-TlH>^d?t!%FvA)L5ubZovHR~Icqht2hya5^Iq zQ2DF;Mn|P^+`DT+=-Ugj95;{H)bi>0QgaH0DdUdxU4QGljE;PO&9v8x*i_Hx(t_+5 z@c0W#Vn!03bb?|@!h)oqF&r5l1M?%bk#{apL`OrOzau4jq|E|b=jPX?( zPYrzz}5dIa|HxK*j_44%$q!>71skueQne14|{)4xb)$4iOtp z`#illN`Ddek9_>K zi^;So@yHPLSfn~{y-UVO=CucT{vFJByOJ0#$KtM0n9v2~iv3v17)~5ZS);uqArT7` zIxkj>syw9Gt}16#I~NbheBf@mre}Aafw&Z_^nc7#6PeGrS94SJGVP+ZPxvxO}O2IEyr2=pO= zf{Vl!8|@%!3eFSu^qG3TqE*?04`70mEqeB#Ds4B6hIH}P_sg>KN=f(~IBE_LSEP>` z#skji5ts$2YM|wC1o~@kKmQwYSonY-HGd?WOG8W=OOuoaPi$wOL$^X*wt0HmUS$Yq zHPkhe-ZeDd@GlndY})d5A`@9h*J3OzZ8MCc&Mk&<3!Dw);T$!LZA${iyA`38=8bSm zP|&I&KV;;LQW_j8Ws)<}C2$^+4Sd^Tsd!tLhH(xNnWRwyH?@kr>d1ZdDFzWN*nht0 zjfw&CW*8sC&`k_;0w~Y;c>D6pKXl1RwOU*uB5eTs!OJQXJ}Ke9G%j1Y{Cf|L z77JC**RO8Vb$TiH3hrv|ICqkamVa3SW&>69pI)!@_YSJJf=|D9rB;#LggFlrwCIw= zm7=t7++~z?86hNOmCNO~rFelic;Q~7Vh934d=_7h5l348MOFPG`Oyxj8kb9| zdc_s&CtVH^4>g<9M&jOFPSp)Ve`~X;Cye-eb2$&DW%s(0{c)oXtuG~gj(z9D*@#P+K$`a<&7Uub_S6Ky;(EY3Xg_ z5o6$Qd9w<2-c87k#>Lb7&3H&UCZo3zmmnmXtTT}Z!78?B9PTz)xi zL^;2aooA?OUv1@OG*TjM^mLj^zLv(|{d1KIjO_XEAKY-~W6iq+5PzJFZKf~Ryf%9d z;)uIW-QabzgQL2(L4J^C8N6fjCACx7baV%`OW*D21oCrS)1qAl-rejaq=n=ZD9`#$R&fBG|9O5-8WT+$K znv}~?<4GUdWRCmP?tj4buI-QOTPk{SrV!#~*$GRMBJM0lhp(YFo0P#(544r&u+X-a zF4&gJi}Xar?HhPc6AoRpp;`H1+N{iGo$&2mBfYC>zjA?Ay|7s~Y!3`MX?0h24S6zO zdpBxQbrmce3Lp~`MV`eqZ1U8O9ovnleJ!d<&QrCWCE{e6t$&}RIdo&PXd^`o$(kV> z+E{)_s4W&FfQLH`%hXLrk0rzBJjEVxn6e6bV{#XU6WfkVMVwJH6*WyhXJP4CNr+t% z4o_^8!Xq2nu%F{1{kFUsO;6T`)V4y5j?$LRPDB|k zq9{^g{-XB2*X_UgA(6MF)UoEVH%9ref1Dk2`|K?do1G<&t9NgY2wLnMH0+S`FZ5d_ z7~4q$1uljm5~v~D{vm~=AQ?l2Lbhk`hpdTR6bokKNmN8hPs>FQm@9NmdHH66$#zAk z&J>=eta?KK0hYeIHs^P}O z{?BYexyYT58o>?NgbN2`0zGL!kJ`6LS==6_?=Gjd4C%c}7XOaGx5%|;L!Nm@fuK2JCNoQ~gQ z^J1noz3s%o)>XuYc71qip*8B$NbY4gDa^PSHRb4CeF^iS(e(tbI3<&zf9qj(>d~FB zhrj8@C1Z=Msx9gOVtOw;-$|YQ*JEi7OUF+ckL!^6sKy&v-?Ps!>O;(*&HA2?Ua}}+ zUv`+_rgdr$Ax;F<@^A#1BGKKY)CYp9H<$}GiPal3S-}NVKR<(Knq?~N$X`A^J-e^- z_gHw6YS1SVh)ORjW!{>8F#HJju&GYA>-MKQj-gdy62-c|y;QOENaa!jw=UVpegO z%gsiR5d1b2QNmhSi2!84q|mD<5kH75)e6IZh(8!-3OD`quYWQLe}Bbnc}oRrW^rBhQ(ZLUrJjToI}V+6G=g=nuSmc$=L=qTU$b2v5>1zPa@ z8u@29j^W??d@s6>e=AZO&h(aAVyd5-weqbM(2JsoRzRyn4hNTtPPN=Pe`_?_`DfRC z(&jBQg;ZruQBlgYP^1=(BG+}b`^{6Ur_B5Fs3I5SBZ{)abhU`3-GL1Q-=SGiH5Ci9 zH~%ql8!@qa+XU?|0w3jI4laW)46xCq+>JyFrML^lOa5p_e>Twuk}+%rVP1CQuFNjY zBzMiwD6r6p$(wfm?DYJbDFsw@v`Wug1Q5KP!oQupypKJTjg>- zw!-EYYTsose`Dg75sPcODvKh9j8s+DOdacH$N&d&*NVkk0B>J|@dfZl9UfLS`S%nh zD7PN#;y=f@TQ{r+N}-9zgbf;8{bBb--g~e}F;*E+PC)1A)pgWt$d5znlp4U@HpOmw z4xE{*E8bOiYi)pJaiTS)C+}d3>T6rrS!QQ=bL}r`VjwN@T zHa*pMCO65HGjj$78rla_Vdaj$caheF5Q!jU)Jbkj}IBS*kRSJ20QQEq-fK9l0f`mL;jICT~FpNA|rJe~pf!a_5VODwln*QW@LtJ&CO8&)2^~`zFK9 zQOh3;eTe%vau#~mO~>69WlwryryerFWHyPg1~p&JVjJRbT~1YUUu-3VJu;1f7y;| zX{t=G3E9C${tBEKkEyz5E=NdpM&ER z)H)rZ`O-w_Zp{e)dRA1`e`Qtuf2n&D_&AR9PPD79>T7!L>A_$ym;*Bd;sypY1Ck&x z1W)kKSvS3NpH>-H9;j8bf?wK9{q^0D&-}^0z>8h@-?yj!$tMB-Ke{$xLpk>VW1}*(y zPf%5jS4*m@O0PClHP~}dw}QPp&F5Ru{%Ststr$jY0q*>YlA!x@dL<=SUNQB+ksZdC#Y)=3uHU6~ zUuWp*6S{ufe{zoL`V%sh-{#6|{NFJN`=E6sRj7LqB$M73@Y$LZ#hmC}zx>lOr3ES# zixLZ{Jnf$6rEpZ{DeH>6ey&>T4zgVhsMlP}qFc0F9}BMio?BwST?Q3GLLN$8=g1}1 zR*#`;n*%9P^m^}-qHNxjzY?&6f$rz9gVg+#TNX zG3*I%>JW%Mmm3l#MSK=EavMd}40m;uQxj6GoWn2*-HM`X27>3*#Hp5DWuQ1&;`Q^e zubuqF>Yc$xf`6v5r0*Lba*yF0u?KA=8*&cBrutI>7d62Vtk?)|NQY zvSq2&f7jF;Y#k{(i0=NTir^Of7cays?vJYEeyqn2g@byuJF4I7tIw0!#!|LxqF0WH zMk3q(7gfknKC(Vb1Jn$KOd9wLf7N2H=2EHasNuUU`>JH8JT%AV-7Ua7UfX|RZ47#Q zJ10@Aqmk$&cyVJiM4boRa&+XeyM_4wwP$#&i}`f7s(=>usqrRW2%{ZOlKOC5b?(%)m&>(N27 zX2Zq4u?ksz&euzBCfg|0xgS=gqg)kXm#}cr-c0ML ze^A1Y++!2yy|TVGR$-IgBqWbPax5NmxPolU#}maPEClNffV*Nu?C%#zj4(=Kt0-<| zBu16UWJXTo@e4eO__M7$N?wSwC6n~^u?%9IkS(GtZXx6Ha|E!ncyI#KNM}a2%C-5A z0k&`MM(Vr8hb)%Lu;O{LCGh=Rr$Eb=e_4c`y8OwH#Zt5B@cWP@o=wFX;q>j8@-vLP ze9JC8`7rXpzvsKmvvN$#^WS7;`82NauFfxVDa+so5ot;v>R72tvJvah&2x8dJMkE~ z`H&fnY}$9*^&utVe_}kG z|2(UWuM3Hmt=}l;I!0?Hs<5TJPJR^V5_gMn;|&cX`+}s&&A+|H76ar@XgJuMH!Wpy zf4~eMB1MHtFAb>~&(Fs<$-lBXExK0og}m#FzJGd`0f@(h~AA}eC))r z?HbWcHA*KMQ1naKYFHpE*_Po&)*oL# znJoALl$ZP>-^Tjs&`+{1zca^aLBBjR>=P96Vv!XO1BuI>vtUhrj=kU%f511R4HH@9 z;o`M#xmNedMha|hxYeh{y;cgKB$~wY_R{%;wWrRD=PV=hfkh zHxb~E%(wHL!RVQGlvf_NWAR0oUs1AW&x*7+#}aWJ3cFzcfcuYrD7i0dng9zPvVds) z;!UYMmJG<6T#deTDiPW)irYg8Czl(^xe*5v*=epr+4P@+8>me;1mS{1d4wfVywk^D zY-3`~BJIK9PW|2xV2eLX05mHlbZ0Sz1ef12WVvI`~vPfvDJ z8jd{hJfGa`-SmYkuG*Sge$2MyLSq#x43S<&tK7i;PY5i zuCu)k1S+kwfGvA$f2m8<%d?EahKtm-33*j&Hk>cd*n9WdGvz{PHdWFtFJ@+DGR4ca z(u&Ha&BEVAFsPs&$MOhyh5oAg6R~lFT9r-VG zud@BKpWSX~gNi9hM@@BMK{by`lBo9cc43vW825aw;O>S8Y##DGF>Q4lYm?x z+73n0zIidQ2k_ANGSdY=p1Lk%7@f5ReXzP{xDyEl$*V$ZE}W5z8Odo+jaAs%7db*M z0|EUe@h<*#g0GofnpuJLy)+R)9gT%o7Q}HUed7E--)(<(I8C zjYc#TMcH>~Z?#d~i&N<$B(l;2!tOixDpb4LJbv7~f4asOr6s(Cd{7soB*th{upyce z_6SGTkzqich!eoTuKQT>IbXbdf%JOY_M-wGZP{em@8l3*pg$D)g=IZ%S>z-JQ~ne7 z*Mu_=*gP9c=Nu6KX^hT3t-bi7W})1l|1ET$xD0s-?RnosJ}E@lw+(rof6ApSvUWjj zC*heSe-zzIdG}InLVTT~UkN&? zVjY_u=UwOU9fze~o7nsT90`jP*B@37>vPebf9C#Hp0~DQuUjweV-SM_QEq}L95}`* zx7{spotX~n-8Ql-;mEG&lVZH+aI4PxjOZXJtW7%2bssBkVPj%yH=w-|oiyM0S3d_N zSldecVXCwg&U6^+E{+E{^s9Mf6C%G7!y?kdA4PQ7pgr+-#qMw>+<`-exVNkH7B>@D ze?OZ@ISI84N(5;s?lIy%od)j=vgzfeI9ZC5bo5`MY2x!55e)>Q&Hd!E=6{H$Qk}XR zQ$9^1*ZhFmGMDjyc?8G+s*~jr71#I-YJ7evggEOj~$8^BVng6MxK8C4YP-de;TyH ziH+lPM-o=px<3#i(nRl`BW%~wqe#@!<7z+Hjh@-i8%=D;M&m78;x7v(><$^70UT#W zYE&l$9|ENx^RV*9r8h>qho`bx^2Ahne}K_*j3W974+8tscfs{+c51krEKJQqh8VlV zGGM?m2p+yM(tJ^vvFgqkufS3je;feRYgnP+GL_R7Gqhg&!((LUrba`w0=6lca*EuN z$l90Bj~G@%Cz=+Em5Z~P$lLc&ajJ1_o2Y|;dpp^bPDHyu^eU+ujp=sCaw3J^sqT== ztAPGo5GL8|v(iR zO`(+%MdK<^-og28mJ}$=Z+upxE!{cQ$qy3J7K;-+fz#1lS*`1mx!ZtqYL-- zXU7)Av3zb++|8b(vnTFK_Vy0B{wH95nq>8~5Bx}x4lYA{wW7n8HH-(yFSNvuoOJO> zla>?`A*nRme^OBfxq`<)lp{<&^o56-Z$^>h7!hEZJ`YC{;*fz2)Lj0U8O*i?k&Jd< z#^ka5$Vh&yd5()AU6&T@u-NwL*P0v3phK}nw@u~#oi>w$=rle)LTU9QQ?&E17&L;|QeELEi>fzB&T_R6DT*GgDG3Ri29NTzeY93=xQ+#Sw-{vQ}dvYg+?2Uct^i>P_{3kpO1=g6g&|*=)Wl8 z`S7S0e;^8OwE-DSYLBW6n~k}vqjKTmz~-nJ`>)L(sw80AqMFSm6Intc-w79z@m`_^ z^qo7Q7oAWH%ILsHEL$3t?;gW(;DMIA(WPv}>u0Z(}P zzpzi825X68#xwUQ4!~@O9C^qqb5AeDYXH6idfiGQ}106}Cdw7=3!g)0=2}89}?sQkCml0s)ZloWwG! zE5@y)>!ZY}vtY7bg)fhfRT6Q~pkiNK11)(LkfojicQsPXbjgOcDbmxyDiYAcGK9|!I8T(7PDBb&2VRHal6^r zyPeN%3AM~QthVSRyd@3|TAX@`+tr~}U1b$SS9=z5YD(u)SFClea9}eq!xp*8f4yWM zTYQEC(Ue6<&^XN2xf)yec26XtRwz3Ktv0oz_XDvxWIfw7QKIHBq+XhhH{Z(_Ji0-l zY{4Thu+>S^#@(fk5uN$oK_cv(tnFh<-R5#UxM4VH1P5ySYn9z1QDa@UVc7jy9GF&V zKtqd>Iz7`JPg;8kTl4niC&WWae?ZpQBQJHb`7u!(^z3V3y#pWM4eT*3X>Y=b+hW@* zO|9g4MfUkU_J)Y3_D2@C9(><--*<4UrsXQ>2cCRjOGVp{dI zqEkVFuTlrEIZ-zF3gs4}PJz3PMT--MG!nOeMhz;Txi+&=j>&)4C+F@>v&i@+B`){f zl#~0U(yKv+{_4`74Lzf(&+L9s*GZswbAMgeuYX84j`ZJL6#M(rJ>_!CjvYc%NnUe=#Z>g7MkI_Z>dVO%g(t1LmZg4yv5yfy(sZ!`+^V0;dHD z_~=UtS?J*|#oOUIxCERmm(IA`3M%143HFA=(*uS1u%c?8;c$LS)<#xF zP83D~p49UPWyk)p3pi?yH}Rw$6nr zZgoqos>GF6C9;yH0qWPXFu;aeuTB}|BFqC;@5?Kj$Z2y9B+CsqICsHu(M_G&>XLtV zoswrOJ&90E*V4Kke~q$2L9x|vEUIfgYzrMK9DaFUMf*4B2MVE>#%{w0soFt!Ta>57 zLW#~#_R0QWj2wL>qeQttHqf(giGA4#2M)Z*n9BLmdsA`K$pZvx-ZA6liRr_$7aemy z@?W5_RBw_cCbM0=ho>tC1poXA{xT1Et-8jUx37KNy{nf6f7p8Li(>9PJ384w!!u@@7}f5DH~oKVs{8TG#9dG*Nl3JWz-PaWL3n|76Rm8%7+<4P(%bV3SO< z{E3LI>6o;7t~g6_1 zfJs%gBv0utL(xXRH^92qeFHsu3c=WtL7vU-!qb%g+^DQ2A#p%d=hqEi2Za@ygFmp!BCk z+jBf$e|N*^eG*X+u?XG_;%hdf{W->kZzwW!blJyFt%~fXjpC zcRvb6K1@P**Bvk-?}K}FpO04ur5;?4eN?!PmClDg4h`gaC1`mk_;+3>XPXVrq9s~E zqvQj4JoXCH!8iu5u){I#423&ed_~FaIb#WVf2@Ou8T~T!G(5yex>~}($O-PAjC180 z?P&AwkkwVHALQx@2kYU&zQVcH*?4pe`CzvmtPcR+VW5666l$0CH_+!?0qrBa37`YJ z9oJfV(J~$wwk*dqM@CH3v8>^av*riZE9sf_vbtw8>lBys0fF|TAVsZt7@K~*B2T5p zf5+?q;^r^i-aj(OYt9G%a8{>$(m6CYf*2Dh;H3m5IA=~#-KQ_^pq2*LB4PU@3 zlb%kZT>~YO5uv4T=#>Y?+OCV2$uB3Yf3G_C%Gh_Yvc6~=+{E6npKV7u0Acf|+{?*+Y&IKWn=F0!;?<-v7eW9}#d)>d~ea^++cY5i0r2BhjyE#XkYh>v?8rnZz2 z75b(tbi*S5(bBc%4>gD5bQ7?#+KnqGddc)?w7SRcVic(@wm4mFx^0qjE0GxybV(ct z5^*9>eaGQ*L`NG1Vsf1WC-k|@bi`vQ4Xp<|0|VO|_3NMlHma3msP)JtbK z^@EDNcgdTp`8qo!Oz|=A7Cny>pQi#8c8lxTVDdal+k!HVhP^I3A8aYl#!$Tn%_U

b)(Q^?=uLn*3CDb|RE+d;S)~^SXfHvMf=#1pn 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 delta 97219 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�Yw6(>3h3Ki)UqBdO#joC{%U+==re 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%0D~$K`ce2sG7I6Ovf>7$u?YN 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 X^I z^Zl{ko-y_Gxh@>CtMyU@5pP^4vkpEnFa4;U<}eB^^K_jzto(`3+pMukTegt234ixs zDl_2{F!OLsH{uQ-yhyQ4kN992Fq7QYL3~{{YJO4BOh}_Gw`N)f(adZtHw0j=Y&u(Z z?U+9Y?1Aj-%he2h(Ab_?Wd1Y<4#L9m;e8zA*qdTS+uJcF*I)joAZ5mkPx2P9?@>bs zJ&T2o6o#OQ#wvKjlz7|hPA>@*Ewq1 zqM!}Fov=n^$}?9z56m_~6<8-c1Qf3|Mb=bd$=Hdzn5}viV;GUozfj=1UOS4JO|&@M z$pOlxLBe5cl%db{kxo=DwzJq*Y`v+}<*?&zN*;~kF4j)4LZR?F48{YHMk$P}+09!cRE^T6Gxv}ERd|<)CKIoW=$bgSLdVSf${Ob^=5*MdJfKm?ziCQ1 zHjzmI=2ZLn2W-lkvqbb z+n6iBLywUn;Phd=iDXTOtpfH{BJ<(>B%ato0Z#6_ZyLEuYJ5|R-P)tsnuyDvYpmG7 zre4iYNX8a7`eV$XrR*4}6Eps`Nh6zTk?_7?)Lx3<9ChSIt4&AtZnkpM(f_mIYOB3RY`VI_alzYiHYMuWG=k&U J{{dQ(ETLfW-}3+f diff --git a/static/vendor/fontawesome-free/webfonts/fa-solid-900.woff2 b/static/vendor/fontawesome-free/webfonts/fa-solid-900.woff2 index ac6a6e31392e5ecdb6536250c885b7e7b702f779..978a681a10ff0478581436eaca5c5695c97445d4 100644 GIT binary patch literal 79444 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

%4CPgwanN}b5K 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 Cp+=wp diff --git a/static/vendor/jquery/jquery.min.js b/static/vendor/jquery/jquery.min.js index a1c07fd8..b0614034 100644 --- a/static/vendor/jquery/jquery.min.js +++ b/static/vendor/jquery/jquery.min.js @@ -1,2 +1,2 @@ -/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="

",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0+~]|"+R+")"+R+"*"),U=new RegExp(R+"|>"),V=new RegExp(W),X=new RegExp("^"+B+"$"),Q={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+I+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,G=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,J=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+R+"?|("+R+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){C()},ae=xe(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{O.apply(t=P.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){O={apply:t.length?function(e,t){q.apply(e,P.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&((e?e.ownerDocument||e:m)!==T&&C(e),e=e||T,E)){if(11!==d&&(u=Z.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return O.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&p.getElementsByClassName&&e.getElementsByClassName)return O.apply(n,e.getElementsByClassName(i)),n}if(p.qsa&&!S[t+" "]&&(!v||!v.test(t))&&(1!==d||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===d&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=N),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+be(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return O.apply(n,f.querySelectorAll(c)),n}catch(e){S(t,!0)}finally{s===N&&e.removeAttribute("id")}}}return g(t.replace(F,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[N]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in p=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},C=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==T&&9===r.nodeType&&r.documentElement&&(a=(T=r).documentElement,E=!i(T),m!==T&&(n=T.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),p.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),p.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),p.getElementsByClassName=J.test(T.getElementsByClassName),p.getById=ce(function(e){return a.appendChild(e).id=N,!T.getElementsByName||!T.getElementsByName(N).length}),p.getById?(x.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=p.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):p.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},x.find.CLASS=p.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(p.qsa=J.test(T.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+I+")"),e.querySelectorAll("[id~="+N+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+N+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(p.matchesSelector=J.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){p.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=J.test(a.compareDocumentPosition),y=t||J.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!p.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument===m&&y(m,e)?-1:t===T||t.ownerDocument===m&&y(m,t)?1:u?H(u,e)-H(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===T?-1:t===T?1:i?-1:o?1:u?H(u,e)-H(u,t):0;if(i===o)return de(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?de(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==T&&C(e),p.matchesSelector&&E&&!S[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||p.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){S(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&V.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=d[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&d(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function L(e,n,r){return x(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:v,!0)),D.test(r[1])&&E.isPlainObject(t))for(r in t)x(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=v.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):x(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,j=E(v);var O=/^(?:parents|prev(?:Until|All))/,P={children:!0,contents:!0,next:!0,prev:!0};function H(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i,he={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;nx",b.noCloneChecked=!!ye.cloneNode(!0).lastChild.defaultValue;var we=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function Ne(){return!1}function Ae(e,t){return e===function(){try{return v.activeElement}catch(e){}}()==("focus"===t)}function ke(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)ke(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ne;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return E().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=E.guid++)),e.each(function(){E.event.add(this,t,i,r,n)})}function Se(e,i,o){o?(G.set(e,i,!1),E.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=G.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(E.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),G.set(this,i,r),t=o(this,i),this[i](),r!==(n=G.get(this,i))||t?G.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(G.set(this,i,{value:E.event.trigger(E.extend(r[0],E.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===G.get(e,i)&&E.event.add(e,i,Ee)}E.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=G.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&E.find.matchesSelector(ie,i),n.guid||(n.guid=E.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof E&&E.event.triggered!==e.type?E.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(I)||[""]).length;while(l--)p=g=(s=Te.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),p&&(f=E.event.special[p]||{},p=(i?f.delegateType:f.bindType)||p,f=E.event.special[p]||{},c=E.extend({type:p,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&E.expr.match.needsContext.test(i),namespace:h.join(".")},o),(d=u[p])||((d=u[p]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(p,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),E.event.global[p]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=G.hasData(e)&&G.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(I)||[""]).length;while(l--)if(p=g=(s=Te.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),p){f=E.event.special[p]||{},d=u[p=(r?f.delegateType:f.bindType)||p]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=d.length;while(o--)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||E.removeEvent(e,p,v.handle),delete u[p])}else for(p in u)E.event.remove(e,p+t[l],n,r,!0);E.isEmptyObject(u)&&G.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=E.event.fix(e),u=new Array(arguments.length),l=(G.get(this,"events")||{})[s.type]||[],c=E.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,Le=/\s*$/g;function Oe(e,t){return S(e,"table")&&S(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Ie(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(G.hasData(e)&&(o=G.access(e),a=G.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(b.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||E.isXMLDoc(e)))for(a=ge(c),r=0,i=(o=ge(e)).length;r
",2===pt.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(b.createHTMLDocument?((r=(t=v.implementation.createHTMLDocument("")).createElement("base")).href=v.location.href,t.head.appendChild(r)):t=v),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&E(o).remove(),E.merge([],i.childNodes)));var r,i,o},E.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=E.css(e,"position"),c=E(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=E.css(e,"top"),u=E.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),x(t)&&(t=t.call(e,n,E.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),i.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-E.css(r,"marginTop",!0),left:t.left-i.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===E.css(e,"position"))e=e.offsetParent;return e||ie})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;E.fn[t]=function(e){return z(this,function(e,t,n){var r;if(w(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=ze(b.pixelPosition,function(e,t){if(t)return t=Fe(e,n),Me.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){E.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return z(this,function(e,t,n){var r;return w(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,i):E.style(e,t,n,i)},s,n?e:void 0,n)}})}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0{{.UsersTitle}} + +

wQH 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 literal 75408 zcmV)3K+C^(Pew8T0RR910Va?D4FCWD0_|7;0VXs7N9D+k>)TT4=sfWq!0 zR8(}{wiU?>ETuHz#?b4$sY>Ajn@$z%m=SIV2&7z%X8-^H|8GhbGPeI?+}O4~B48jG zRBCFKqEJX;Pu&FZP4to!n_Nl|V=R(ei9wPy${$pxC`TBiAI2di6#x|h6#*3i<&?U& zaY{4j#UJ5WEoQ^8std@9fQo>Mfbzb@ocrP=7{fHH5$uY9ihzoM>LayNK8!HyPc)x< zGqXVw`Vy9=RkyN0Q zz=GR$uN+Yf0R_TqfHyR{5imSIx8CyqWv~GogN@OH4O;hCL~R2`DjT&SqNISinG6F^ z46wi`@D3~t^g#PgJUyc^?)3D+xG*jmv#X*TuE^w`HfH!=@yq7Q7C^;Bb_s)eZ(>b$ zM~jq7rZtl2tN1odTOa`7e*y%|n5eVlw_mosfc6EbOjdzVwl+&GyS^BhQ66# zK31MROtA8-K!=rY5A&@)h7K`c&p9yXVx{k@GdE}qo{9sSASAu#$3n@^!?$-~kNFIc zY|^f&KcW0V6}UWdVC_e?HV2ya#aiuF#u1!PTMHmuI%c7N}viDzVs6o+>FYjqIquZ*3J=71q}X| z%~Efb{okWKGshPiL*nV_-5t|xmv*8n9R!J9`d?}wid>F4NCZZx-1~1clRSs|cD4%= zeprY1%|dxpG*&CM151!N#Be{=)aa|7TbI2kFeGdc*gOyr1i?_?#23npma}lUsITg4 zy?Sc3A)po-2AmjZ7z9iB|No_G-&dWttD4ETsu*nRbq^^f%kJPP#}_j|Sj~-OyndXl zg%=xO3(V|L0D~-f#tZzApt~(N!@;@J@NnlwP{JM)CSlNdK_Q_PL>=m zuwa247q0fDXlGjKwnHR+=@7~@am^}u?s0^Ug_%%=JWxub5MTg<8!f1$rYU{TGM2vW zhyyz z(%II}lk#yxKJBsJ>i~2&UW46@2H9vfMF0dvkfMY@NC={ZK*}IUYM~Y=(U3eVYm%&= zly=H#s?dNy0i-Acq^VC@rk9kg2_ed!q|u+~@0}b^8{#RS^69vTdp_=U_5Gy+6a_&3 z<+#JL+|s)&+ATg`ir%RJ6|f)_jClRt zp#roFLihhnwMs#mHCDT4tP7GgKDzv(1XaJPnqAe^t?q^q&;U6w1P=g7362^%sk;Gk z8ck3`a^~%cT&J0J8l977oz9B@DV<1ZW+bg~Hpq;z*c$lzYhLqsIFL5fg>Ao4!` z_xO=*{S7w4#CroQlu?3-3YfJ0fB(r`{l~JU&zv3>P!XZ4pafE0I$7^uG1W%ZPV(QK z5XB}owh%%c0TT3Ud$-pTmC-6X?1}x8G!z+DbdWi}KOD7hOp5^p*#atx17!qAAR!5; zz5D-V0N98374CoXq7?I*y<^gKWbD$SWG8R9tOqmjq5qdcgp(s(yhR77_nX$FT<(Y@ z{LjWlSlH#_K?}i-e;?`IEqj|=R^esrP5aqEA%`tvHeX$dSZ;6g)EWDKH!2vA;u-v& ztj}FzCr+8RZO_1Q6Bn-EJb2isag(Obp0|4a{!f~}aLJmj`*A}~6f`U{N?Im19&Wq= zVGuF+1w}wYLBpb^W#Hf!JSH|iIW0S&l0r^FC~*>~X`Ef%y@6mD znn0p51yZ@jWQ8EmL<(DFu)BFM*$S$8a8R(x?OXH z&%0DnUDLwe#nX#-UqT`h(u%5@dIm=3mNxcI{)s7T=$V*Vxp;W;?;92wmz0xN*3c3d8kv}!nVVl+QQh3$)gu@d9iNz-U0&PJ z+`IbvspC0+(KF}HKk%@pp898aZ0+FY;dNwgCFOrCtJRhUs|W zVnM)%QVhfw;=CZyG?aey)z-4w_pwhRh@2NptQS=Ei=yWnm$&`6QTevS8hIm0WxXxF zePyr!-$Y@_FIrv3{Bj!xMqb;$uAIX1hUWI}{U~3_x#QrDA7kj98!8q z)-NJ2Qgs{WJ0mafjGx~+zRZ7(_g@^8HNSvO{P<~ob3kx|V01oySXR~4X1u=vjf(x8 zM(O(NiIIJ4;TyZN9Pt0fw&6!RYG~X1eqN93<#asUTwR6#jJnpya zez)DMSIhZqIvNi8y-@J$-FCC;cG~rFAs&lHg0C;nkN0U(r%aMKZmbv~Nh#CcuOE*u zpWXC>6(|B|P}^03VpemTog_K8OiZ(lu7wt3Tr z_3PG>RV$V+U05p3n?0+fc>0vdmcqVkb5jvGmZ9O#>v8*W9osT=O;uz`WGTT2+;i9M zjnAhvGt|>n6mXe9IIR}5QL9qQWm2Jl&*QL}SPTLT^3h9`LM~;qm<&3NOu%7L2%xXG zqXU46hLVH;6&Vo@O#RRs6n#x4{TsyduUE?%sZOies8#dX!1r9sG?3b}bNkjZ>&BIf z7S1oV6wEFwwNB48#Eo5>jMlh!^8bJYHqnPM`L{#?K>zVWr=2dh`Z2ki5JNBrl0gsG{BjVhUztg`IT8u^r%-GlxW{TU5ta_~pEqYhfC5`NBE) zWgSXsD2kOmA_3PpcauuJ42FjH--FUWclds87fdtHL$_RkM0S=6aNkWRQm~Pr+ z(*iVNX0Tmb_=n~L0EQS24$K3F{J-jQ{fe9JzY)z?D%tgeJjnqm|>j88kn)V2~ zBgKMjEQ+=(s=%~jL4@xJNOF?4l@a<5O$&F$I*q?ZzE~|* zeEgI;a4e_iwZ4ee4ze{i1Ym&|&rSsZuh&H!ZtN?noD-q>;0f?&7 zGne@s!Sy33b<|le&mTM4i5vwl*=o}W@Yd@fMU?FL019Z=i7{ zM0p0Aw6bT~ZZ))BrVO1;QA*>7W1A`dPRF#E$2qeb7znkuw7jdgpdbbKal1U?4 zynq{giV?>S7yA+CxWI|lI61s@f=jQ(6a{}n0nn)$!n+q-o!?k3Hq6rO{48ra3u}7i z+2#K6sFXT(M_{>DgGr0!s8;C+yd+;J4iJG@0B3B+8w%s4G4ryhyH#DIF?gm3AQ5e2UQ``FIR zr;1}gY)wchJh=wL3JSx>K@^?y?u7tO2~E%wdU0XQo=&Ay=9#)~A8|bjc$jrdGfRQn zEaw6?HQ)#;37n)&mdz0if5HI33vrD&iE@j{{s5T-ax>}oiA;Hp7lNUJP(-$>@F+k` z=6QfZq>gQxJZJ7yD)ayfkbs4kh1KKb#k;5)*6>Gd)0HNbsAIQMVn%XkC?K()SQ!S< zC_-sUcn_F!6t&>>iYua%pqvt?nD0+RhZ+#~UMva^K)gNR<&oCdHM^Jh+wE@o02J@* zH&E%#9wnrh6ws8gSTr~VGOVzQv=Sr$P9P310B7(h;Na?0xCltH6pv<(7|F8n2rmGO ztvQRcFgtI;ixEu9*0%9>m_=)xLwh7mFd8WI6mnieW}qoTa)z&o+~Pab79!i{AlK@R zvW%_lGi!_To>g2(t8`Bf>(%t`S%HL+NnVJ=yMUM`bcvT;;bn>nN`W4y>G6srfXD#0 zIRgk#cDZbgAz8@JjsKpjVTaqQ2YTCXjq0hu0`T#J!X}9lUX(2sgf7usS~WJj}hOS^3?@yvp# z@H@V#aD9(Fhw@>v0zd_0Du^>|#Z|DTK~);&SQ0n`qj)h`F%m}x5BD7DX)wEIwjk1}~ zbIgS?k9kiG!OBX;Go%I%U=)Kw5c3ioTkn+Ly)s)eg($V}i;3B|b1lmXO0Dm4|Ih+e zS>2H?+*SFfKK59tE2wP~P~zh%o;aPo;Ea6&r68{@Bp&KMU64dlRb7uA z->TwTz>i{ltAxKp^3hm^xdd>dKEtPYRKN_<#)%gbAW>CEhEL#MX!cTj?R~gq6|P(n|QEX;$?h**nc01n)wIn`k-D0 zm@}A*LU?ijyC3|g7N4Im&lPPRE~~rQbV&ijX}2of*AGT2;~JJ{#lGdH1!%5LI|1Mk z0G-37qWd%h*#D3NN7pdDfuKp zTal#!uR4jRL;;CZ6e0=9mH;TkLqtlbj#vZXPqeWL==&#V62dUax@KnwLGBLf^BcJd zkPy}$_-i`?{md@_^|zWVF$D->f*DdrpKZ`a*Xk{m2kK+^i7Ia}I9=LfR$&1H9SjI5 ziwlkMn#3e@8W88HvW9k{RVJt%#CgVjN)8kMC@zGfM@h}K)0}&kfpN#57A!x=N}*Q@ z@_%ac0TZx%!6!)u7@SbQVjoZ05(P_#7&0+I5|Y_9Ts&ouIzU}9N)D>$5!4ViV_9I0 zSG~?zrz$tJlc8Ot;gLO0_wc0Ta(7_4eAJgrz4>a|p(jGVcGE60Yt1#6)FxG}rM1JV zTDc!qme${@yAN9%d30(f&@xXJX?TVyl4vt7!xQ6Rz*A*ueEeVoQ6pR^^RVbpcw&J- z%qThV6PfxUMbg9E*h3P<#A_%OboDA&d6*S}smh3mQ-j4!o(*7^bwC4@zq*9#dQl~q zlYZS2UD#LB+DfX6WHeWemo(P#6jWPH;e*qPUo0IVz!xQrakYHD7I*lD7MF5n_M#Ra zpF?(`dfc$Y*9!p2rP8IE_TY7Z^Go|``jI)ZhDN;);}qas_chJACr6y3o~Oy$r7d7_ zTSQ~lNRyeI@3V%$C>vHl3?Y9w17g@S@QHbrcM%S9rVPZ4OMPHkQdbXOOzku~uFeF0 zvcdq>_B?!f>0mS{EhX)RyG>14AZT5;8`*n%{NT;czALrG6jd38J1xhMc0#fj!9sxh z#k@|m(Jq^1bQT?>qN(o#Nb|`F(QqeKG5{QZp$Jf0Teox_NEJg|SS8sp+S~PNVq=MB z+KIQNyQb}=yOSJ1&1l@iJdLkNoN2D^L|nPgXVsKL+Gv1+rT?`$Z3K2`MkHOcF=Ig| zTCX41a(74_$Ff5dLmq&nur=f|VB{Sne%J<`=}F9S-V4%cw!CG>!)itx-2mTWWXQr9 zV=7EWa%b{C`lKVETNqr!E((?%F3P6JRdPqyjMbd#wc4U+PU|yDpT9x;1_HCh;0k;1 z&MCt(-s|TLX_~KtiW@8D@_NnuY)26`JBERn7=i&UNU)AJ#$aVs7zlbd`pSw-!C<0y zaT`IJC`$BX)pHb|t}*BVhxt2ii>#VuQENLr4a#7avF{n4@^h;=mgE`ZQghkX23qOF zSh~8{-qVC>(21T@#uR*L%v)NFB6wCG!B4$wqDb|Y(ol|`k3g?aT?2L^MA4%6&|L6Y zeGR|q>XbRA&o+l?p50gbYP;;Qpgt~N-}ijvSfeCRTo@%wewF*io;@~|HuM>=Nj#6QPcA;)d$47f`_Wix+&ymw zm6f>pz$n=T1CYz4?}?w-*pY%8?;6+b+81okbV-abUCTklH4ea-a$O-MYG6Q`z!sX1 zGN&!gMFq*ZsM4~DSfR8jf=IfrF@Ztu2+v^_C$zgj4tch<3!agi&41#xbibFIn1o#T zu%0fm>)T|6M5;TZzr4#>EY$+ca9GEI7&jIhsB4@tjp)*u9t!)y#mtRLk1v+5k-{;e z(*a8UR4|E|If@a{>ieRr0TJP(9Z( zpbl^VaV^B!BQptivtO;?db8i0gv+gOfwQgWk*I9vvt+X3-f3PWAplaupc^nx6kB0IXB%+vSW~qN(*t_BA!fg&@(^B zWQcfM>G@QeSO?|7jl#vG_jE;p9CDUQ6s4gaRKuWI+@M#i0)aHrKSBk*sWrly{pylq zb7+Z22$}Bj!IncImJ&mFGINCX+Tjtv(p@&t=Bzu(@4JRwG2h*75ASP&zx0~WIa3M+UZ8(dI!%Ev&|kiSFWP`evi7cD-jrmV$Y%!A@S zu6-t;+c{yz)$9jj-Vcr5XDwARE~23Nj5m=Wro}8tgWBvSw0nplLrfIR_3FwK9?4=S z5(HCy3TTnKYYEh?aSRI0U25HQG1KvZMUz432fV^Oypj}WnaB)7${ahfnmbJ3)F_eu${wL7Rv8ezpM55)xWqF;p0Y-g;oweTo;j z7D3IO*XFWFJr~Bm#sV^bBEhSB48RAB+RRBQVhNxRzRD>cP;Yp5Rx*WKOUeZ=l|8Md zBc&X{fz4yu#i*7;dA^>>6)xqn7YkYUFy?!z1;g^h)=^vfUuYzUN_|DPmUeRI9i_Um zzH_TsT3Xw_vv4-zWf$JRy}PJR9bzVW?NSLG60_e(+``k$oR5DChMNx4qV2 z=+vrOCsQIbor~}0qzS2Q`)@w;r~BXi-~=&kcMy+SgLbK7=8iI76@Pl_SgoGnoQaoT zK421bgWC5M3wyL9i41a291VpaR)q7>pwcMt^1%U6OP8-)iLChi`OWK~ZjY7)t}I`@ znpEXiuWsBDsG#S%@P&z{W&K)ZHkDXOY98YEBr+mbkjN&!cN3F3E8RQ{WA~eAHjyC# zOd;5X8XMGsLqZWK6-=Ft8KI&_noyGe`P;1m${jn`ToIc10slYn2o1s*-XNABW?+?A$$W$<%*;qQ{!ZqqNu+8bamN zJySL2#_iNJ$W<~Y&Le7gOx>E!-r`Mvgc#He?JtF#+?P7~E94!nnNU6@ruZbg}TX2)N8_4 z>KG|=fxgcD5@tSO=B1?Kps8tA%mXaF-&{7Q=%Pv~+Y0f|B|RU_&@XeovbCkiD@93G zvPNb_E++%kBmoi_=Vh0_Z*``JrQuA8{5r+(6xwPyLW%Sl@VqB=F_;;Z8MvMd2vDP8 zslW{%3dl53mu3g3cg2CpqZI_X8YGzlBMClA-ze1 zm4<1iPvq~mnPJ$^{OSkBX}0f_@%v0MCk@~$T`86AiY}~IbB$jB^{xbhe2<$~E~7=T zdbM1!imYUmfC1Vj66hY9=j73%w|5;8EeP`Xwv?r#6^Dwvs;I9WL?gm8%tr%ttp<3_2%7L&v_B6`=QDx86P&tl2 zOp3oV^$cq~PlnM?NSG=1cPmmV52Q_^0O@!iGN8ipEDv%*5@He99+oaC(>!eHGP;li z51u%|OCaq_bi40ik^ogwxJ%V-A{vesh)-Iu!%t>dd+|@yHE#9;0?w;C7w-`h9}zIl zX&TiM5vuHrG69V#E+G{Ig{88GcB|H%pl)Y0+|6)lXCi1&8+rGUzynJC>g}DqeO((UO>O79nOzh0Ym3gxD1(^u-_tec;$nvXFbxK? z1!;>Bh9q_}y4~;g%Sj&Y4dcowyL9ouJD0HkYzl@(FlLh)785xr3ZRIMn*!Olu?EgG0Ga z)&nAlyw|XHw~-81%nzau$=cC$SAZy*V)evV7K1%;3#E0N1Kk*&_CCiLuL%#Pm)!t~ z+)X{-S^JnO!MHi)j=_>uRZ|8WfWZ}B>8NAqLB0f_OTILyU#nlCYl_#VpFucvr{Xy#M`~vB&rm{J;KteF}NK_#0oX3?YaD7Yo zPgm3djJmv!fwj1JRY6*(m&}NDmb(>0j8#*5j)<{GR&)$tjLn+QQO9mxByVN{d?ua_ zF6vr*2^f3JWJV2QXpk)R*Zd8x#|=n5=K#tL!sFb|%uB(Kd#vUd9mZ)I8&%Q;OEKa< z@v;)O8R~4+7ASQx)z0LZJZ*M0{X=aXP9qLm83*ZTnbQ@#!7;anw<0Hlq2VDi>Pz!% z$wotLLc6Lc#V3d2SnfCp;NGgLP2;CSsA=kR8nsq>>Pjetbj_n; z0UebgnaN32`)3oeqjImLZZQ5&<7I<8Dl1e7oH5Q!dXC=%@4X=AffGu6^bb*uapMd0wMWT7l$Yrb!Bs~^({&x#x6W$sonx?1b)vIE-{YUD zDb?(XqXZnp>|&e5#Gu=D18qMNXU%=W-&;rXK&P^0`eVCR`tP)rds5r}otjVjw&;&= z4L+_XJ)bnVH}pcc$zWE9N--`UAy;(c`u*$n;xR-2m|Q+$7l(6##RJCv-*#J%Ev&5xj)Ez&4;$ zgGV&2kjXgZHQK2LYFaiKKNyoL?xm))ou0X1)A~xmSZTU4#TxMAEZ8R7h?~&tku2nK zQk*c$E*8~RC-+620&h)O8rTknQCP>a9gS=|$&nuv4HB3w3|a@!SI_``$~4gzVUG?F zljh)M`o&7do?RL7BGC|!Y%z}Elh;k5VB zE-rCa@TEhn7pEZ{N^$S#He0pgOIk9eIXM^?XZD7_?4Y@W7DVC^34iG_^Ji{#ZuNRD z=D5L@f(Qb{)_X7edXHJ(t7G24+@;z^49#0M?-(nBmQtg-@Awrc>e>YR_P!$K3X%QQ ziZ$+d!Iv)RJ&{64Rkr}a#<;cU_-jaju$yq-2U$UC|GBMYZ95O*K^};2Qvz>YtJ*?G zKKfe)NER49>j4`yf>0>=9^Z`_8Sgqy&rSoZQ5T!jxp%4dQq&T)@hf{&=}J; zxft~9b`lwJzb;DbQ-9>B@(>A38wi^j?Ne206^k#GPF0gd4OZ6g*SBg1-V{{zR*<5W zAso25Z&OE{aBbwP?`-4aC0;*8-0itXN5hpQVkUBO5wG|@cLIotrrFcQb z@_?{G2jLez%XUxmSN`e?nL47~(E8N7pd%LUsJ5rw1?xxQ8|X}fEoBd@fDPKXIrL0- zo88Y261t+W;Jy*w*Oe};YH0V3v)*F5a+BWS zRRML|xIyS$aGz$+;Pme3SKRhXI|`+Tv6w}2$8sfZTYNByREo@MR!3@0jz2BlywmYA z5qKQ6li-em>$41gh-4`zgVre&959uViAf`z+&+Bzt0?RONDzWef zeBRyZtK4L}2j{eFN{%U%GAl z`E%x(6V&$FT9?^DX@AX41>EE+G$ruKxMadyzxSOq>YR7MoG&}m~$hf zItV-1;gUvN<^j_elj7{U)-gJKMuF_dY>Zl6e(ZRxK_5QjlB3P)lu=&QU9Cs5RA4-^ z`U2K@&|Rah-@P`>DVKL@f#S_O5jRvp44_<9Ve)Oeb*hyq{$g@QRg*@XdPUXkUjD}DfA%Xb@Jz1tcIMS(uGiNyZ%!R= zQn%roUFM|Va>~hhXc;jimR?6Jbvl#+&6z>diE$J(x4UtQ+LE0>D8)uPCy{On80cRN zV&Vcy$IryD+;xEaDAY6p$Egt9ua8WGD#-(qKf{@No7!t)5~ z^KT4xVXRHQv&5|xHTsVjuyK=?TEhH2y0~GS_=cne=w@{P{zn7^)pZP9M=oE=8+0<0 ze*EA;oG^j4*^0fgjD3<)>uOoMQre!^r`w!ixvVG$$+d-&qJEkz7i?UaeuAWpdU(X= zHzsnpvKOuxKQxFvS_~Ne*n~7}?0=4lyG)CG3J@@U3ih0mZsnbVSy(9Mx#Vj4yB7Ca zJ7JzWY0c+@!4SZ^+E2ueeiQ*h*19>0(Bdc6KOPv{Gf=Sa>3 z_DRaEjf3lBJ>r+*G~|f}Kx|3n(GiIq%)}lUzpp#UMr|-`>_y^(pXD(^LH?1EalUt) z!5}`Z$=8N}7mUyOcahNH7nxdf(c|S&#UXQXe4QI5liZ-0KQZJDdnQ>qyTFv+Wpqni z;*PPpMa-$>tc*7Jpca>qr63iot2L!|vKH5`ZN=aUII%Cnqdo(}uuokdHV@1Xw~gun zG4T~!qvyaJaq{R7-R_(Xgydw~)-&(89z1!mKt3*S6r?*g0&h0$r4_tNo6pdYG6&(7 z7zsxA4$Bx8CKn99EQe=Ht%ah9(G^RR^4<0kTV16O$kesz%Qn8AUh0-fWqi}gs~@@w zR@6_18rSlq0jX9FtYmbr1KC$TDCUO@sJ`=kY=>%vYt4xIr9D>1w&2hffg0)=P)e;X zw}PZ|C+aGijnrA6Mv1OV3g;xcJhh!!GXiAyi_aU^W`=A6aN1@xh+(ZVv%DN-9!=Ao zXF+XiRY$vB&m*(a0+i#5pc*5GjR`v`iht(+QSXq5j@85Tw5q&dFqXQ$>r0ul--HJL zyb5$;`*7K-hU7(pSRZyR1MfQuL}-Z*=rkA@+FwW6C?04%GA}>w0#aRe{vM2zq%Z5P zw2$ASlqh2IL~?0~xEs-iBNx@LtQ$vLX6t&bt~7#;5MfAv1O_1IWLov!rQmvH7 z%cYwB7JgNq15qkYR4c4pnyQp}wK7>MrmQ|AKlo_Vc3~wC76`d7SU>J?I;d2@V;;ct zD8@jHeYp#>eH|ZZ23MPc*f${Be>vf#2z>pcQkZB`rBdXZS8y^kA=UExl6Jpd@i!l@ zYuFd#K^LfFYYnR0QCDg4_T!U0rY3*Yp#d$X691Ha+aj1_Zbp=d`$WDZHGcVQ2cJol z85qE;_Llh>5NL7(+dU;Y z!ZytFf3GnJJSYe@+g`e;KvCMn=$Xe?3XSiWvb&=_AioFyntu4Fep4%!(JmBsAXM^= zGrj>y2>|tKI`z*jzw+)Z+N}r&A3D=O?07d-v+=sV?>Hk|U~6O@PoK2aHs1@EddtMT zZb-SH6w&#W&#W<32|O_r36_4fCt@O11*-d}H0OUhBH{L2p4}49*sv^(KbWp0E8fKX zWh+XvOfWw*;PGa$*5nTE!)Bnx_JJj08z+w$*2^z}=lURHwnCBgCaM~@B(t~h(?6KC z;?}oFkEJ+otN?O`&Utf|w3hcid*rVzSoZ~qylW78us=@rIW zGkm&HG*aYON?QaqmyEY!$oED0nJu9fEn-6cBY+tmUBea*&V3-C!%~zMtk4^%lC`BADJaw<}2l%Bl#GM#P&tYHMR22cO?)WE|_v32N8N4zvh^KCFw<0>~^qMuI zOK?>#C9~+sMKFqvNXIk|1nW3{lSDp1RNmF;EWtgjwkN*n`~_m!szU*?xWL9w=@iyu!?@UfYJWLhG zdi`<4Rtbz;eD5@5n);+<UY8PCbSf_OAG}_MuwS!EXIf(P-sNrK4Gw&vT z@W}qZK%&D_tfvE2F78{!M1uhdmQ_CiCEFn56VRcH=@BtjtU%9a;uo6l9?;b`Ir*|( zDeBZ-VOTt&;Kvd=ua$4bi_bKt`sga=;XsJ^j71|MHF6DIaO`l@aJy;9znAOMv`$Y8 z6;{gCY?&T!BvH(j_b4+VwLR0~k^7DLT%V`_44hx`;-1^^;?*u^;+4N&Mc{^;4-+QC)a)2|*kY_ttx z3fTnk+3YO?m!jn6o?cy!Z-P=2r@gq(Kb~GF?>9{*Ja#hN4U&wtKCN1Q@d&V-Q2KRh zt?qqtVohiMovFsHbdhuK^~bnv`&qnMwFEsx&phDfwBt!6>^KW^6z|gQi);GS z<0C7;4bRg(Ff$XYp=7<7!7drKTjxo=S}&ilTotS3SN{AhrfJqB~!=SsF>I87^7?v*we7Y4Pmz5{P?hIr`KNpu!*QLT30F0W`~cU*Y1Eg zwkh>x2w#e>9x%6T6fKAD$|HdCpXHEcRQ?uFs#Ye8-#oMUSK^L zwn{qnM$K5MNW1n@&3JtzeMLyIY=~(nVqBo$lmaBZS5L)~46~+{PfQgkb#y|w$p#Fo z-A{(s@MxenwHnJMkPIB9!Jq51R!g{d^86uE4uz_I10lXpBdO~)YfN4HVhqD_bS0@S zd+apz|HK&rrVOYJ!sX@g4x0#c*mJPJfN|=xP+(W;sCk$a>0SCKoV|8T=v&V)#WP1S z!Jdr&Xn&1FCn-(W6+Z`U5f!9;|%A6TBoeg$11IeL! zhPqifaTd}3%K>E19sB=(LCqP-Yn0hyKXM$oO-!W(7PX?j4(q2okHCx#crxWh6E#NsjMZPMsu zR3Ia=HaFz`ZW9>wN(K?0kn`f!nmh>h{G?T0-;!liAjK_VuY5<8pGsGmGWdit!B5Ds z{MH*`a9AS-E0IxcU5#oJo^G?LB~tiO4dmsYF?1Iwu-k~2gPIb;){=eMn~rujDJeZs zvu7^N^yhdy*P7sW7bt;sC^#UP?P}*3C;kQSYX-)+35v|sBoJ(XI53(cje(7F#>Kc- z>t%=8g#udpb3zP*Fc4vDsBFPrCZgqO9c)+bm7nw9tV_kl9{_=}iQN8~-?BmdDd7H8 zwFU+#DJc_8+89KseHu*t^u#0cY~lQxad3`_{|GVoq!EcL6FQu)su+uUTE1)(mjshq z+Ob@?DXeyJEoY(uvE=&=#Ml}yn?lTua07RY#9>=u2c_wgnHXQ~BHa?Ga3(l*wfUGx zrV2;w*P%hwgyJ!)v)!h=7LO#&fA?ksN~yi$*k#~2kk4%q>Ie(^jOD7??IU|Mr9H30*V>%j(H4E?9nZX>+cYjtd+pL3P%@TDDe^XR4*bp;oFiJO zWYnlJ_B0GC0mxvem2`sr@ATCnVYS6%Y_u!~B;<$)*OZpA&ca%CnfYZJM+np4+S6E0 zM6J(*-y2Tqu-`?<945OGmGR<+p5$|n^CYIPifoEEL@wKuSDnJ5VT&qeN7TkbF1&Rz z`*`=9W;}nTn6lXf33oe+VvhBsew%Ak+yLop3db&0x!8Yuth*-P7{aIvdjk=R2k=k& z_Lko37bfn>savJd@%IO!bN9+yP}tL~(CbrmWq{Wi4>=9nisd^U;%K?Y8(Hm70wF>6 z2)lo=P&9Lh$x}jLEbzGrH54T$qma3(!6RZvtZ7kOT7!mN>Ub`*UNbnXl^4lTSG4=^ z&-fZM!|%1sJt6^toLiH4&_t@k*@d_KlA11A?bo;@A^d{uMKyROo@aLAR0MI9=XOrt zu90EAVCPvJ=KYk`*kdfACb?j#tpC%3SlgyWPY3P)gdPirAKu>GM=i_$6I{p1S+#kp zwPf)+({_@tOa=*L8lhkFpF{KTEDB{?BfG9kQ+n zVAT``>zG=QGf2Z0m>k1H6I6ow=bJm6IQNMn-#TK(COb3r-Jyf?%cf5JTOi@C_I*mLLRjhuPRd;auySDdf@B6uO!zc7q?b}992_=1&r>8k5gMV~TML;v z`fZYj)rR49piWBtxT ziF}DJ0G0H#rs7AOVqhx-7cr6uq_h{J7%i%DQuR-n8VK2h%5OOU#ta%6zGXByvxv)6 zrO^~qZe6Qd#yQ)MCatDc5?@AoPhGZpd5k&(vD+hIfGBy4)XXO^%NT|T{5m}e2_u}u z^-``CtN`k=90*!UIPZ5+E=#_{6YM(p;I2Zy5Fv%I>%kNELF4co>)o}2aH{ANbn~$2 z;G8M50QXD>VN+=0+246if%S=00UU>dO{p0mMk0#<$ERI7DJCIDPnBPod9OI`$Gu`C z6-vlL1-H;(VK!hNmb;~o!R|MNQQRj#S`-(1!kZ19qk(Kxu%&*?8(Fc4*fG0(dch_` zagQcvdUC};#u6CF*lepMDJiKK(90F48js#2clmJFW*vsDh$(!RxK5+}e71 z+CoQ8STVaxZp*q-`AUWrNd-ewuNAdp#WFp>l_c9GHqrMHr=w%6xo!!9Q64whrelHE zG*o5J0nDQPy^X`#s=i8b522*`*OY{1++vV>b$Kwj{Xw$S$aQF%Vs7xhCqKPA!D->F zcU9i2>86uHWeJ{tLlPV9oUjVOCWy6{ZxKd(6QH7NOyqchX_Uy zwJZTq(F&vrXB&in`C%#o_(vvAk4Cl18yN>SkC5a9#yZzc>d@Ctwqo^F5BIJX8=)-k z?^`c7H7_0PS*ZlPLX|`FP0?Lvvc~%wa;7#Ve&&g$tgoEqKd@#cA%EpH7SFS(v1&p5 zENe4y_M2$(<~;txPqY}6AktCc4ozro+(?Y#PmRY$N)?i=l~>GHcsVjlmZbGT*U}=o zI+FzfK!}^+<)OKjlXR<<___t0-9`g;zNNzVkUvEHkSJd2YK{PfHK0W|I=Tb8jsWIy zm_v0!nO=(q+IhAE>nIDnsh*T6x;_)M(%5r2Db;#G^RCc z-?otT`0Z{H?9^|vF;rZq9g2I{#u%$oo3PSWgIjG=Qhhc|Q5(h)lQ4jH^Q_J}V{Txm z&#?INKPF-5+~K;G+lfA8(50g9hB?y2Ah1J`H+h0QQ2GI4{VzkZD6O`B!*}b*gq7SI zX!#yNus5yXx-c6&dO{}5P4n%J<95Q*yy>1-#Fq=a-@bg6e^n|}n@;kYVKbo!?O}Zu z4nTdtj)s@vk!b_+fYxX&Z1`2JI?(11^z@b0Vb0uTG7F|&ci8bWtX_F&bU5Wn(_W#0 zZfaAT{1M!8qUz8m?JO;=LMbZU{_u2&w!EQ?6LDJOdQlZnb?V`9K>2-KiDzxxOM4YOL?ZgDM2mq>hDXdaEoPE=3Z zS1MJiZ_VB%`BaJ=p=tf4^q!%ssy})pinR)}q=T5gIiUnJg_X|H!o&*#L`E7D?QB-C z1wVRkER^r#EtQ&GyOZ_u(9}NrS^91fQ9LiJ-iw@)ljN+@J~*qH>{v-$?g~I0RK#df z2CNNhnY|&qbnUJwZ=ax=S1Skc6>wFvaLrp4FmP^UuVR+9)|64a(3Wvi=|W4#-sAVg zJ(Qv_DLB9?UoW~CB@3g>%X4C9`Sj!0GlOPDEkl9Dk4(K_41e|GW#Zx~!nX#PV&&EO zfYgEVIl`)5N#cz|lKK*J#~lnrRUhJ~14=gh>%BJs(?s~K$lKPRn9?nc)Sj3P5_ zVztQ{#){p<1xhuk?W$COl}H?_Bm*+A%lmUF*bM!OD+67EaTqX-h7X%{632rh5FDPB zDD?`YAH~gA#F6m;F+k40UtmWrVKZ*GOnmORp9Y)ihuF5NaE|}VVRA{b4m4@sGOp;K zQZ4LMSoJE9ZeQYkZp=1;@b{I5fyr8PaIX|~TS%T8Ru3G6;Zmxsy7s^>x?`vV+v zA*;J~g%)-;B=QX=H7h%}x#oSu=aJvG4w5yF#@FymX^n`P5VD`5k+283gh)TCt*zO@r#}4vwj& zAko%E%>72Mz907os~^V8vB#9Pu#u>%p1Q_;u|q6V@!8}N?1sjuQD-%G(d;TmFybuf zOM=Py4x%bwhD285Yjt0oYr9bRG7&@=i(dszD1adedx`O3P(|#r8g;HVm;;mXiNQcb zPqYYG#48eul*fky1QF6hAX-ehBk-qE;JDXQNEbwNQJT>8PdQaXnuP?C2*?Ry(&Xy5 zAk-kg4AeQ_x$2PDcO|7$Wb8z(Acj8WVkr4vBqNrr0 zTPWst_Mh-y?CtuVS~*qBd+EQ5cYq|6`aRH`;RdZPS6yGmCZjBdeaiT+Iu5AjmHQIv z$~LIm6X_aKLN;f)gVUM#8FGQ-Oidq_kUf{k^Cx=QYYjF+zzW8(M5UF(Ew z+XPAHD)bJAzvKRJJ5J<M&y4)oTk?&_itCN!Fv#y;A$!ndwJF!-+$MFx^*=8 z0)ZeyPmD+j>A6H^KbzBR>BH%Q6{KG!?@Cri4?MP z`X?m6C8V8FZv6s|r zNIFjHIg9bl?X@&bV!-K0*;`&LYMP-m^T4U2YcGRTE ziOO8Z6gMKaA;hua5p&>wz*$|Sn|Y6FI?g1Sq&8cfq*&g|XctUV#dTm|;`9H%n9F6c3j6M5oQ7I&30Sh)g&7X8p zuxkI90n8b()@B81ARxs zn0uhfvki}8NwbkKPjxmj?*;8yQ=@$9$SFm(kScgu5cHDR0 zEoMcNv)0YWvAG9*VgHDp)?6(y+w)AY*6zO**q&$m@^8EV}t1Tpv z3#RuN;+t(`Px;?HFmFparb^xoM4VXhS&razZV82^E6cNlQsX0z=mIODkO;kPLK)I) z<&ymnIf#nNlYvkcRN};*=b1JcV2YF^FYAtk{xKySr!tQOXRSUVy(=hz6MZ2II26f= zZkg{|tldiC)YZ%ugH`irc;wtmjl3C_MEMS{!hT>_g~b7zElHA&;+~3i8Xcvzs5E_p zMa5x#COxUY{01@MyJJRH!WrO!eOR=J5LF!Cz=0I^v)mSSz0Yh}4a3HpU-(+%op_4T z4^O4R{|HIo7}2&^rG6m+%Yq(-J~{|{hiN9Z25@2KNKJpZLa-{^PkQVAVU^(mLj3#hb(B^m|g;>X3(R1 zebMA~>Kf+)(G*ibHIC;@{SCD*m@i;(TP&Cvx0nnYg}LPG_@1bJCP`(RfRQrt&>ZkG z+Zv$6Ah7=@6Qc$cfZtKzi|pD~OyIe3BSm^eKQJXyTC;2q74$kqhK7swGmgNq(S2%Jz`Cg zD6sS;%9qj?z93W+Q)$58(cDPT`6Z{j^PqQkguM+Te833P^4sl5tF>$2!wqwqq-l*p zP@yHI538(XV~FJA?Pan3OUuiPP3?JE0dE zL&d(4OUhqx1@ga-nVM%uaeQZZYd$7+J0)5oUSdAY1{aUe9W*-A0}4c>^l1-*2^bMx z&2APo?P8|QTrLfJ%BC+*LKjov=&V&5{Fj*!*vUK{Tr_R8E^+SZ3`5}KOO}s1F8Zk zA=M3Y_mnY^!z!7{y4XNEW`+z&Eiw(u9C%Mz7E8#qC0X(vhZ&4a5WsyPlt_XCH~Z&C}f(}s${jL+34_I8Y5ydSisSM%G9}T1zNzpLH9WJdzfXZ9=3O|F*tKv zfsW2JtOtoYgKN4=-HstCTgcS^C)o_awv8pwRO;+>KuHzWqwFxa)~$moTta%HYHR3m zjrcssVL(NnLP-+_wE}P`k)+iG7q6r|ixsHdC)}w^=5l7I3eC%D(_J}|-Dkd1cVDGT zT6PI^>+jLIGXWBMBkyKgYq$VlCS3TaL!^0WFamMip$AOv2_|}#JuJ1tB=f7AWE2BKcDt^g^sJqu+VWP-5CS(rzfHDJ0>OZ9ND#Anc=rg+ zR_)+{H82jCs*WD76OrZA+~VD1^z6=Q5lX z3Hv{_;~-PM4{ZCGDD3b_y`B*oGb?c{;Yw;kcN{uhVQ0{7pbh%K+fdM03iuCV@42NM1rV^G8(ppMSpD=y(>__ z5>dS7ATbbTE%Y$On{j4sv{*4Kk}U)^=-A^&VjMxdh4~aL)Y)qCXzWK8? zDYf<3rxI)+7nIdGDJzFjnyx~_HtXEM&h6V|v*H)dLl!KVH}+LFx&8i|2Q@qX@+5ft z*^rnNqceqfZy+ggNI)_zN*42fZITFhOW9h@LMShHrAqw~X{d-U6!@e^i3-4x zfX0&de6%r$7}F7_k;n$h5s5zBh3d`1xcq^!+UFNje)Cl-Ye}^WxFj; zYK5UcYfZ+r>{3Bo4r-p39iPsM+}pJBgqR zo7OSyIBaS0)2(LrEv3O0?$mNdTS{mQpRHf?FLghamZHnG(;WpSLp6Lq-DnyFSUjer!R!pq7J}*-S2P>V3^9b8l_c(dG`Ht=yO>3xTr@93=kCfA4rOSk+e1rof$^V|Hp@@H0JW zcx1F^g065hb1`m3oWe;$x?urUqGH1!aFGUR^i07qsxK9mbLKx<5q^G6QEk#k z!j=|u&ia5nG#73U%m6<>X!k`iVE!O>l{&iWP}VCCYVyfsurxWa^gs;!%ic~)yE75U zD}$Gpv;TR?PP$PYa1DDA)5ZeVijOxU;jb#Hg)(o5g+$ACO zTAsK{#lP4jt3psJ2bAofkS%Xy@o2U7MShu9wm|SW&*Aqa(v{h_?^1qqP+QP{T{$d% zT1j!~0(ksvc@2+=jew|X%w|P!yHo6qS_biQi{;jRxlO=O<;FXinIWfQ-erbeLnSJg z(=a_M+{$;BA`>$DTw$XT)!@B7AqF)ssiAH*#C_kX?7s7KDZQ)eOG`+Lb;w^j}9O|*8t)yNeieGc>6+Ja9IeRMD@Tq0pmm&!UYAU>X+ zrAUD+X$C?09jj7LLUy+~}SA@;Og1!|8y9bDi>$XZIEN<8TD40K~Q$o zQ=q~g^rr%1>B?QNrbOkPqX#jmHs6Q%!RsHNt0u2*ZK;$ivrUPt&bNyt{dJtaoll)! zjQSr|a%}Ti>zG@hNW53 zbG>e~L7be7sRq80!%Pz1UfM~mj$6g(RbL!@tFt395Kgc>h@IwKir!AqGLDN*5_>zq zwL&QGgbQm_PfkSx-=R7?v9QEacQxgnkW`Mm<>Bc)YKxY#MXL9Nlxl-g^Nu0sHJ8qB zaPNEETU*+i78<&FzNjo-Att)9aMfe8+yA*ZDwbBB)@9{jUHYe5(UNUAbtqhO8@jBt z0nKxHi?|$N#NDe3%v*=&Of)RI8SWD+)25^pdv-%Ice(!J{Hjum9@luL zx|V2BHv&vKR*#>2S#1n|*1hsPRXiCGrZA*X9DPW(-+Jwe&mG^6MteqC$FQB4$shOq zO*>pPMNV|D>|qm2NuFxI%c_5Or(ar7cEnHwS&&Mj#VAP5FK~;mS6_^#uunZRCw!O` zqf)Xi(;~#ei*1a0LstGi`nU~RB{deWLp)OqeP_>^VB?jU^8TfLwfrX}rnLom65l?o zE>EpgHjWpMzRnkqZyi!OMb6jR-~TBVke=sHLsqYkeo(Ec{OR$Jw8q58YEyr7$3C2g z3nx}HR;(9M8ZC|Tzr&n`uY#Kij+^DCt zowfjBTJGhcwqvZUBSUCWl3ima(4P7TB^$OegVs(y+FuL6sc`C|745i6pYIR|l+OaA zz?w&~fp5D`p@h>4`hEG~!tpGq7il%!E0o5CKo+Wp#vt|iJeNJ?7EnZKb3unEc;~847KG4tWCvO8da;C9&^~H+B+Xly6Z0XJ3 zrIsIEUOXHnYm9G|eS3!X8S@3JH1+rZ?w1P$wu!OM-eMON3DXmFiH$*oQj7Z2W)cfR zs0P;|6!evAGg2(@Wu3BFDpXoU8m+1ltQfC&QWaB z(uLvuGPiZ*zgpV5;U-_I53c-D@M-`dk@*6&}*gbC1+wUhfx=~7< ze}k&mi5F;Yf4Z84mOyqdIIl!0&sg82iUJ@@XtURgT#hoS5o^$$Sz|s)tql?UbV#tY z`n5>y>wIm^zgo2IUS`tf6%R^;U98OBpUkQbHLf?v%hZb z$T|z*ST}M!U)1eTapJ^MNs*Y?4)Fvc=8?YHUOJ!47c&+K#WGL?4F}X9qw(8!1nAIg zq6i!|);BvM-LTMP3LV*GDBMBMkHJN@C&fh#1-4uQ^I^sQIijZ$@^g|hVM(C$jI7oj=w;qBE0eEY-Z$Bf?8Ap4_Gu6_`;)8LzHi)!GZ&!5g)K))2ri(|5002PEw{)NI|i3o$6B#@0D2h zU7|cOHlmCit^qEgdBJ~5$Fw`q@9ZihicviL&fWBMlOFMh*lp3zi-tOnxL(4I6-?hE z@w}a-Bp)A5n3zy!a%-DpPPBTN1W%#|+I2=2F|(8lTX94fVtKD&cCA*L2z>r;P(SV* z8-gfVS1|>)WwN(jC~jnOgXxc(6 z>|A3z#q9C8>n@gNad+k%C&w1=Vjd|Fnd_gQR&#E#cwqm#9VIGx$yeU)j0F#0?Ji!K z>t3Bxd2X?p|L6yuc~$E7Wg+Mh&zo}aiZ~7ITBW*Kyn@0k?Yn`3EtMW=&eKB4%Vna% zchOemssdV_x~V|kS}xxQttAU!6hEuhcC+tbnr|huS0OSF$s3IPJTVKbLVHl$%@Nzr<5f*OA0w zEV{0%lN#5*W7Z~6o_PEk(sNx5@c6aBeo?D%w+t6J-I0!^2R8kOz9)iu@JQVT9|X@e zu~QElr?}v0htyDYaj}_EmnI}QjL;f;BzR9TgsJg~${2|_E?gg*kR(=5k@N$&KtCH2 zWMc*0-I}qoG|`kO;}+%J_%o2OkGQ^s7+6)y0IyXDh8S?LT5&nZdbG-p&vL?4qdWLp1E1B zrA0JX21L<8__>pQKv+C506WwhwDxjIkaLA)As5gN^Ro`}K81)=8$4bZU}hmFN~!Q% z5*Dv)w>WbYNmj_s8oZU7(Pj+U9GZl*76h|XL-?ElEz~M$$yI{!c;s-cQ=OXiZ-4Dl ze$Yl@H55mz;&+6E>nOg7kdSTT{2O(qN6C`3?LMNCX}HD60B|>U%Wz}47F~rKm^TXn z4b0pHeBO1$wOj{4jChP@e&`~72T-xf?Th>U)oQvFTshELst-NLX?YAn?vtCdN5!F! z%05;7#f@*jiSzfiulvIjnJp5%9^W0QxG3rcactG<>mM2t+^k-Gy$UvFV6QX9v;e6D^){~f8b6(K@DX$~Y$r4>t&<$GW z0p+azNE^tm$At7Cl|gVhs%t}}BG?tUaou)h#kj$-ByJB{_y7*B6h;+ zr||IV+XH|(&F%MXx3MOP%Teq~Ry7v#(f7PZ?FTF!q_&6}R^}b&c}kc|Y!CS|GGrh( zH$VLU#68<`FIO_<-IlP0myRr5*Y)F#o%Q96*p*#c-71DfbaU!WS%GT!x1+aQ==8Xm zz9+CRyOMeRtSdaj?MOTq8?XjDR2%OO3>HR~7eZm6Xy=D?D0Z0ZFW_ z5resatU2E;uq5yEQRO%{xNYBg zeRq;*FL1Rq+u*FU=(BxR+3Q)wK*6$j;BsH`%{y%dd0S@8O1x5RoLi98yKj|i@6I?Z z+^(qT4Lvp$?~I~LuJ6=V)r@Ux0Wk?JW78+t_-QJWyi;{{=y`VJKOM}8G2FC-puVQ8 zWxeXQT9lNBdEUl1t=`v=2_7gCJus?67N0hEg^RKmHI)$22O*`l9Swy_WV933lL2WM zXd;)}Ox0)*0@FYv*K%wv?HTE!y(vD$LN_)fJQ`*3M_SXIgsKkREU+oLIPWNcB6LX> zUc}Qt!Z?Q(x>3kU?Frc`Z@IvoRbsZ;$_TSOR+ZUOn>wrDji9=gV&=4AVOmG`PNi(x z5Tn!ikSTAxS>Jezi7fsQkm!`f786{mXl#_rug=p&HF8CTF+;hT*TW&|sZRr23>E37 z2$d5f|9xY?^0v8Xm`0s_#~5$C0`|yo8(QnLfJ%%3&NFj#O#Sp-VzV`Yz_6KlvGD^c zVe@<_!mnPVt5&A$Bu01kH-g^ISRz!QbB@Ne1%p#|eX*e6Hg`l>)ojkV7E)79%*S8Q zV>n3YCaA_t|ALGdYzd3LFZWt!bJ3Se44Pa>J|^QCW2mRPZh^XErI}9wd}yw3Sc!dh zfr9g|!r!U!cahB@708$PS)Iw0jNeC;y2Zs)m>_J+l#&aHu(9Tmx1V#Taanu85q{iz8kX`5ps9euRVA?)w^r{}zT^5H=AJS>`4;g-7- ze1}->X-g-JRk7{5xx#tGBs7^+JHX4Peq|Lm9Ak&_B#13lX)Q!s%9W|ja!p-C%o9n& z`VH>tV`1d&i9ZIqV&yP;j9^I`&-;6#JyEnT`1iv08{@?Pn2l2?8PD+B84L%k?FWZ+ zSZ>5?=#}1Z((3ARn_=9wcWs65uk4SeNlK?2(ulPO7Kf-xomExGvLhPhko7%az_&C2 z3AFI{ZD+t1MGk~F$OLXE9 zs?8vc{eH#EFP#SmYiY+121o^QaHCazre{YNr;rs)d_n$RX?RX2XQ)@!(c!tFft6{BllQl0 zhY>Q~b|Ev!9*hwj65QMBy25f+26R^>-)f6X%6plak$)MV7aknyU93v}D zIv zyE*r`feX_*faW~~9mmYF*Ju`L;<*-(y07Yt!Vlc&l= z3q&j};R5lq!`)_Qd8Y07wTM%%+>np&`;@?T4=FfLdI8jO7Pvh0A_8CX1p>R*HQ1AY z@dAm{CL@N7V|@RlWM98O@kBCShboDKe3e!4VVCt{^hRFquhMS_B^xQU!?Nd z-h7gSc9}G8?F$@`Nc9KMyBAY09yEKaxzUcJ+2JcQT!~Q^Yn~FR+GLz9MV|Uqp_(M* zr*l1(f;HXxt5!WD=a2}~Su68xmrAx^7INdfYp08Q$vt>%#w+9ltJrY`y-BINgXHr$ zz_zjYwYFt;H_aKuswz}IA>mqLZn^2@*;gH0RIzJE&1>85Gl0udzAubx=)KVD_?Dd< zh&hj;LAc1sPRh{6Mu$AW2bp%5^semSa0#E#lTUhm5#76XFr8(~=gW354=jt2_|DFB zUAtxBDE1$VhL$ga3|`o*w2)n5u~10;Pd`q=ZmA+BnNBccgV0RV9`NwR^3Ba>jZ%ZGe}T&ZUB_E_`%9{y5OUv`x7L)6TgeUi>MvZ4 zZYJN{j?!h#NrRa^i5B_(@-J?TpbKHtKso8nkPA720gDwDByXGwW*I<^H}e%*y4f{_X5eh5- ztUZZBuOZ8A=F!DS+;{Nei&v+0-qDpUmINSz^od ze4-qGjo6{unt$`#N9OVinrz|104y<}cDSldacx3g0izPSp=B`?X`a~)=Sw-FsFpA4 zR#={M>ccZe$<>c`)FMMTK%H_f1KgqA2zfYtt&F zT=DTwiiI5JcaU*cjj0}A{rMoVU|hc&8owZ+=pGbPBc*QYJ|_NZcpVpPG% zbo60J{=YS_q~;vO-83k8%n3aafPXWDf-=|yCPGEbOn|MM|C-fA7UTG8R16#@xpG`B zKr=b?SnI7~kw!a1q0!jMYl<4y1Rec!*D6Jl<5G@qJ^_jZDEr`4f+1?Bw=X#Izi*mS zkV5$i1SdD9Hv3N*lb+>a*q{w-i*b-5r3JOqeM0r4X9>3WfCOizbU=eOc1^~TyV|g* z6Zb0r@j?A~A9gQFahc1jHu=ght^5x8G;!*CJ2lln=iQ3bwPcQ}sB8!WGpdGbJd>DpSv|Idq-iQIg?| z3n}*ELbDqq?5_#m>eg7wb3w^`KBpS~aqsTH7iHDMd_#L4eYb#Zr?_i9`rTrbD$hT- zwXE+-gPBWavBfjo&8%aZne`VVT`oXWL(de0m zMbpfI<_Ud%Qw3-Cga7s5NRhSr-o!yCk5+3r71&>JO6C1rdrcux$`?a*v6#^8P;4WE zwgTkVEH&1QJEd~#z=7hbR_zMu*{$qtxR`PAn+Zu*-FCaLtp4Z