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