Rename ServerOpts to Options and add an example (#142)
All checks were successful
continuous-integration/drone/push Build is passing

Rename ServerOpts to Options and add an example

Fix bug

Fix bug

Fix bug

Reviewed-on: #142
Co-Authored-By: Lunny Xiao <xiaolunwen@gmail.com>
Co-Committed-By: Lunny Xiao <xiaolunwen@gmail.com>
This commit was merged in pull request #142.
This commit is contained in:
2020-12-06 18:15:34 +08:00
parent d9646a6e00
commit 428d334d2b
10 changed files with 104 additions and 35 deletions

44
cmd.go
View File

@@ -72,7 +72,7 @@ var (
"XCWD": commandCwd{},
"XMKD": commandMkd{},
"XPWD": commandPwd{},
"XRMD": commandRmd{},
"XRMD": commandXRmd{},
}
)
@@ -1042,14 +1042,39 @@ func (cmd commandRmd) RequireAuth() bool {
}
func (cmd commandRmd) Execute(sess *Session, param string) {
executeRmd("RMD", sess, param)
}
// cmdXRmd responds to the RMD FTP command. It allows the client to delete a
// directory.
type commandXRmd struct{}
func (cmd commandXRmd) IsExtend() bool {
return false
}
func (cmd commandXRmd) RequireParam() bool {
return true
}
func (cmd commandXRmd) RequireAuth() bool {
return true
}
func (cmd commandXRmd) Execute(sess *Session, param string) {
executeRmd("XRMD", sess, param)
}
func executeRmd(cmd string, sess *Session, param string) {
p := sess.buildPath(param)
var ctx = Context{
Sess: sess,
Cmd: "RMD",
Cmd: cmd,
Param: param,
}
if param == "/" || param == "" {
sess.writeMessage(550, "Directory / cannot be deleted")
return
}
var needChangeCurDir = strings.HasPrefix(param, sess.curDir)
@@ -1172,7 +1197,7 @@ func (cmd commandMLSD) IsExtend() bool {
}
func (cmd commandMLSD) RequireParam() bool {
return true
return false
}
func (cmd commandMLSD) RequireAuth() bool {
@@ -1186,8 +1211,14 @@ func toMLSDFormat(files []FileInfo) []byte {
if file.IsDir() {
fileType = "dir"
}
/*Possible facts "Size" / "Modify" / "Create" /
"Type" / "Unique" / "Perm" /
"Lang" / "Media-Type" / "CharSet"
TODO: Perm pvals = "a" / "c" / "d" / "e" / "f" /
"l" / "m" / "p" / "r" / "w"
*/
fmt.Fprintf(&buf,
"type=%s;modify=%s;size=%d; %s\n",
"Type=%s;Modify=%s;Size=%d; %s\n",
fileType,
file.ModTime().Format("20060102150405"),
file.Size(),
@@ -1198,9 +1229,12 @@ func toMLSDFormat(files []FileInfo) []byte {
}
func (cmd commandMLSD) Execute(sess *Session, param string) {
if param == "" {
param = sess.curDir
}
p := sess.buildPath(param)
files, err := list(sess, "LIST", p, param)
files, err := list(sess, "MLSD", p, param)
if err != nil {
sess.writeMessage(550, err.Error())
return

View File

@@ -24,7 +24,7 @@ type FileInfo interface {
// driver for each client that connects and delegate to it as required.
//
// Note that if the driver also implements the Auth interface then
// this will be called instead of calling ServerOpts.Auth. This allows
// this will be called instead of calling Options.Auth. This allows
// the Auth mechanism to change the driver configuration.
type Driver interface {
// params - a file path

33
example/main.go Normal file
View File

@@ -0,0 +1,33 @@
// +ignore
package main
import (
"log"
"goftp.io/server/v2"
"goftp.io/server/v2/driver/file"
)
func main() {
driver, err := file.NewDriver("./")
if err != nil {
log.Fatal(err)
}
s, err := server.NewServer(&server.Options{
Driver: driver,
Auth: &server.SimpleAuth{
Name: "admin",
Password: "admin",
},
Perm: server.NewSimplePerm("root", "root"),
})
if err != nil {
log.Fatal(err)
}
if err := s.ListenAndServe(); err != nil {
log.Fatal(err)
}
}

View File

@@ -27,7 +27,7 @@ func TestFileDriver(t *testing.T) {
driver, err := file.NewDriver("./testdata")
assert.NoError(t, err)
opt := &server.ServerOpts{
opt := &server.Options{
Name: "test ftpd",
Driver: driver,
Perm: perm,
@@ -141,7 +141,7 @@ func TestLogin(t *testing.T) {
assert.NoError(t, err)
// Server options without hostname or port
opt := &server.ServerOpts{
opt := &server.Options{
Name: "test ftpd",
Driver: driver,
Auth: &server.SimpleAuth{

View File

@@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/assert"
)
func runServer(t *testing.T, opt *server.ServerOpts, notifiers []server.Notifier, execute func()) {
func runServer(t *testing.T, opt *server.Options, notifiers []server.Notifier, execute func()) {
s, err := server.NewServer(opt)
assert.NoError(t, err)
for _, notifier := range notifiers {

View File

@@ -33,7 +33,7 @@ func TestDriver(t *testing.T) {
minioDriver, err := minio.NewDriver(endpoint, accessKeyID, secretKey, location, bucket, useSSL)
assert.NoError(t, err)
opt := &server.ServerOpts{
opt := &server.Options{
Name: "test ftpd",
Driver: minioDriver,
Port: 2120,
@@ -65,7 +65,7 @@ func TestDriver(t *testing.T) {
assert.EqualValues(t, "/", curDir)
err = f.RemoveDir("/")
assert.NoError(t, err)
assert.Error(t, err)
var content = `test`
assert.NoError(t, f.Stor("server_test.go", strings.NewReader(content)))
@@ -99,11 +99,13 @@ func TestDriver(t *testing.T) {
assert.NoError(t, f.Stor("server_test2.go", strings.NewReader(content)))
err = f.RemoveDir("/")
assert.NoError(t, err)
assert.Error(t, err)
entries, err = f.List("/")
assert.NoError(t, err)
assert.EqualValues(t, 0, len(entries))
assert.EqualValues(t, 1, len(entries))
assert.NoError(t, f.Delete("/server_test2.go"))
assert.NoError(t, f.Stor("server_test3.go", strings.NewReader(content)))

View File

@@ -116,7 +116,7 @@ func TestNotification(t *testing.T) {
driver, err := file.NewDriver("./testdata")
assert.NoError(t, err)
opt := &server.ServerOpts{
opt := &server.Options{
Name: "test ftpd",
Driver: driver,
Port: 2121,

View File

@@ -18,8 +18,8 @@ var (
version = "2.0beta"
)
// ServerOpts contains parameters for server.NewServer()
type ServerOpts struct {
// Options contains parameters for server.NewServer()
type Options struct {
// This server supported commands, if blank, it will be defaultCommands
// So that users could override the Commands
Commands map[string]Command
@@ -76,7 +76,7 @@ type ServerOpts struct {
//
// Always use the NewServer() method to create a new Server.
type Server struct {
*ServerOpts
*Options
listenTo string
logger Logger
listener net.Listener
@@ -91,12 +91,12 @@ type Server struct {
// was requested.
var ErrServerClosed = errors.New("ftp: Server closed")
// serverOptsWithDefaults copies an ServerOpts struct into a new struct,
// optsWithDefaults copies an Options struct into a new struct,
// then adds any default values that are missing and returns the new data.
func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
var newOpts ServerOpts
func optsWithDefaults(opts *Options) *Options {
var newOpts Options
if opts == nil {
opts = &ServerOpts{}
opts = &Options{}
}
if opts.Hostname == "" {
newOpts.Hostname = "::"
@@ -104,7 +104,7 @@ func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
newOpts.Hostname = opts.Hostname
}
if opts.Port == 0 {
newOpts.Port = 3000
newOpts.Port = 2121
} else {
newOpts.Port = opts.Port
}
@@ -148,11 +148,11 @@ func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
}
// NewServer initialises a new FTP server. Configuration options are provided
// via an instance of ServerOpts. Calling this function in your code will
// via an instance of Options. Calling this function in your code will
// probably look something like this:
//
// driver := &MyDriver{}
// opts := &server.ServerOpts{
// opts := &server.Options{
// Driver: driver,
// Auth: auth,
// Port: 2000,
@@ -161,13 +161,13 @@ func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
// }
// server, err := server.NewServer(opts)
//
func NewServer(opts *ServerOpts) (*Server, error) {
opts = serverOptsWithDefaults(opts)
func NewServer(opts *Options) (*Server, error) {
opts = optsWithDefaults(opts)
if opts.Perm == nil {
return nil, errors.New("No perm implementation")
}
s := new(Server)
s.ServerOpts = opts
s.Options = opts
s.listenTo = net.JoinHostPort(opts.Hostname, strconv.Itoa(opts.Port))
s.logger = opts.Logger
@@ -244,13 +244,13 @@ func (server *Server) ListenAndServe() error {
var listener net.Listener
var err error
if server.ServerOpts.TLS {
if server.Options.TLS {
server.tlsConfig, err = simpleTLSConfig(server.CertFile, server.KeyFile)
if err != nil {
return err
}
if server.ServerOpts.ExplicitFTPS {
if server.Options.ExplicitFTPS {
listener, err = net.Listen("tcp", server.listenTo)
} else {
listener, err = tls.Listen("tcp", server.listenTo, server.tlsConfig)

View File

@@ -65,9 +65,9 @@ func (sess *Session) PublicIP() string {
return sess.server.PublicIP
}
// ServerOpts returns the server options
func (sess *Session) ServerOpts() *ServerOpts {
return sess.server.ServerOpts
// Options returns the server options
func (sess *Session) Options() *Options {
return sess.server.Options
}
func (sess *Session) passiveListenIP() string {
@@ -211,7 +211,7 @@ func (sess *Session) receiveLine(line string) {
}
if cmdObj.RequireParam() && param == "" {
sess.writeMessage(553, "action aborted, required param missing")
} else if sess.server.ServerOpts.ForceTLS && !sess.tls && !(cmdObj == commands["AUTH"] && param == "TLS") {
} else if sess.server.Options.ForceTLS && !sess.tls && !(cmdObj == commands["AUTH"] && param == "TLS") {
sess.writeMessage(534, "Request denied for policy reasons. AUTH TLS required.")
} else if cmdObj.RequireAuth() && sess.user == "" {
sess.writeMessage(530, "not logged in")

View File

@@ -70,7 +70,7 @@ func (m mockConn) SetWriteDeadline(t time.Time) error {
func TestPassiveListenIP(t *testing.T) {
c := &Session{
server: &Server{
ServerOpts: &ServerOpts{
Options: &Options{
PublicIP: "1.1.1.1",
},
},
@@ -84,7 +84,7 @@ func TestPassiveListenIP(t *testing.T) {
ip: net.IPv4(1, 1, 1, 1),
},
server: &Server{
ServerOpts: &ServerOpts{},
Options: &Options{},
},
}
if c.passiveListenIP() != "1.1.1.1" {