diff --git a/README.md b/README.md index 511cd782..a21da1d5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

A Dropbox-like file manager that let you manage your data anywhere it is located:
- FTP • SFTP • WebDAV • Git • S3 • LDAP • Mysql
+ FTP • FTPS • SFTP • WebDAV • Git • S3 • LDAP • Mysql
CardDAV • CalDAV • Backblaze B2 • Minio
Dropbox • Google Drive

@@ -45,9 +45,9 @@

# Documentation -- [Getting started](https://www.filestash.app/docs) -- [Installation](https://www.filestash.app/docs/install-and-upgrade) -- [FAQ](https://www.filestash.app/docs/faq) +- [Getting started](https://www.filestash.app/docs/) +- [Installation](https://www.filestash.app/docs/install-and-upgrade/) +- [FAQ](https://www.filestash.app/docs/faq/) # Support the project - Bitcoin: `3LX5KGmSmHDj5EuXrmUvcg77EJxCxmdsgW` diff --git a/server/plugin/index.go b/server/plugin/index.go index 66975db7..8ad208cf 100644 --- a/server/plugin/index.go +++ b/server/plugin/index.go @@ -7,6 +7,7 @@ import ( _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_backblaze" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dav" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_mysql" + _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_ftps" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_scanner" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_svg" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_console" diff --git a/server/plugin/plg_backend_ftps/index.go b/server/plugin/plg_backend_ftps/index.go new file mode 100644 index 00000000..6357c66f --- /dev/null +++ b/server/plugin/plg_backend_ftps/index.go @@ -0,0 +1,204 @@ +package plg_backend_ftps + +import ( + "fmt" + . "github.com/mickael-kerjean/filestash/server/common" + "github.com/secsy/goftp" + "io" + "os" + "regexp" + "strconv" + "strings" + "time" +) + +var FtpsCache AppCache + +type Ftps struct { + client *goftp.Client +} + +func init() { + Backend.Register("ftps", Ftps{}) + + FtpsCache = NewAppCache(2, 1) + FtpsCache.OnEvict(func(key string, value interface{}) { + c := value.(*Ftps) + c.Close() + }) +} + +func (f Ftps) Init(params map[string]string, app *App) (IBackend, error) { + if c := FtpsCache.Get(params); c != nil { + d := c.(*Ftps) + return d, nil + } + if params["hostname"] == "" { + params["hostname"] = "localhost" + } + if params["port"] == "" { + params["port"] = "21" + } + if params["username"] == "" { + params["username"] = "anonymous" + } + if params["username"] == "anonymous" && params["password"] == "" { + params["password"] = "anonymous" + } + + conn := 5 + if params["conn"] != "" { + if i, err := strconv.Atoi(params["conn"]); err == nil && i > 0 { + conn = i + } + } + + config := goftp.Config{ + User: params["username"], + Password: params["password"], + ConnectionsPerHost: conn, + Timeout: 10 * time.Second, + TLSMode: goftp.TLSImplicit, + } + client, err := goftp.DialConfig(config, fmt.Sprintf("%s:%s", params["hostname"], params["port"])) + if err != nil { + return nil, err + } + backend := Ftps{client} + + FtpsCache.Set(params, &backend) + return backend, nil +} + +func (f Ftps) LoginForm() Form { + return Form{ + Elmnts: []FormElement{ + FormElement{ + Name: "type", + Type: "hidden", + Value: "ftps", + }, + FormElement{ + Name: "hostname", + Type: "text", + Placeholder: "Hostname*", + }, + FormElement{ + Name: "username", + Type: "text", + Placeholder: "Username", + }, + FormElement{ + Name: "password", + Type: "password", + Placeholder: "Password", + }, + FormElement{ + Name: "advanced", + Type: "enable", + Placeholder: "Advanced", + Target: []string{"ftps_path", "ftps_port", "ftps_conn"}, + }, + FormElement{ + Id: "ftps_path", + Name: "path", + Type: "text", + Placeholder: "Path", + }, + FormElement{ + Id: "ftps_port", + Name: "port", + Type: "number", + Placeholder: "Port", + }, + FormElement{ + Id: "ftps_conn", + Name: "conn", + Type: "number", + Placeholder: "Number of connections", + }, + }, + } +} + +func (f Ftps) Home() (string, error) { + return f.client.Getwd() +} + +func (f Ftps) Ls(path string) ([]os.FileInfo, error) { + return f.client.ReadDir(path) +} + +func (f Ftps) Cat(path string) (io.ReadCloser, error) { + pr, pw := io.Pipe() + go func() { + if err := f.client.Retrieve(path, pw); err != nil { + pr.CloseWithError(NewError("Problem", 409)) + } + pw.Close() + }() + return pr, nil +} + +func (f Ftps) Mkdir(path string) error { + _, err := f.client.Mkdir(path) + return err +} + +func (f Ftps) Rm(path string) error { + isDirectory := func(p string) bool { + return regexp.MustCompile(`\/$`).MatchString(p) + } + transformError := func(e error) error { + // For some reasons bsftp is struggling with the library + // sometimes returning a 200 OK + if e == nil { + return nil + } + if obj, ok := e.(goftp.Error); ok { + if obj.Code() < 300 && obj.Code() > 0 { + return nil + } + } + return e + } + if isDirectory(path) { + entries, err := f.Ls(path) + if transformError(err) != nil { + return err + } + for _, entry := range entries { + if entry.IsDir() { + err = f.Rm(path + entry.Name() + "/") + if transformError(err) != nil { + return err + } + } else { + err = f.Rm(path + entry.Name()) + if transformError(err) != nil { + return err + } + } + } + err = f.client.Rmdir(path) + return transformError(err) + } + err := f.client.Delete(path) + return transformError(err) +} + +func (f Ftps) Mv(from string, to string) error { + return f.client.Rename(from, to) +} + +func (f Ftps) Touch(path string) error { + return f.client.Store(path, strings.NewReader("")) +} + +func (f Ftps) Save(path string, file io.Reader) error { + return f.client.Store(path, file) +} + +func (f Ftps) Close() error { + return f.client.Close() +}