Rename ServerOpts to Options and add an example (#142) All checks were successful continuous-integration/drone/push Build is passing
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:
44 cmd.go
44
cmd.go @@ -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 | ||||
| ||||
@@ -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
33
example/main.go Normal 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) | ||||
} | ||||
} | ||||
@@ -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{ | ||||
| ||||
@@ -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 { | ||||
| ||||
@@ -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))) | ||||
| ||||
| ||||
@@ -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, | ||||
| ||||
30 server.go
30
server.go @@ -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) | ||||
| ||||
@@ -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") | ||||
| ||||
@@ -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" { | ||||
| ||||
Reference in New Issue
Block a user