diff --git a/README.md b/README.md index 32604ac..3ecb881 100644 --- a/README.md +++ b/README.md @@ -19,5 +19,6 @@ Pull requests or feature requests are welcome, but in the case of the former, yo ### Tests ### How to run tests (windows not supported): +* do not use the "root" account, use an unprivileged user with write access to the root goftp directory * ```./build_test_server.sh``` from root goftp directory (this downloads and compiles pure-ftpd and proftpd) * ```go test``` from the root goftp directory diff --git a/build_test_server.sh b/build_test_server.sh index b086940..f718353 100755 --- a/build_test_server.sh +++ b/build_test_server.sh @@ -84,7 +84,7 @@ VpOorURz8ETlfAA= -----END CERTIFICATE----- CERT -curl -O http://download.pureftpd.org/pub/pure-ftpd/releases/obsolete/pure-ftpd-1.0.36.tar.gz +curl -k -O https://download.pureftpd.org/pub/pure-ftpd/releases/obsolete/pure-ftpd-1.0.36.tar.gz tar -xzf pure-ftpd-1.0.36.tar.gz cd pure-ftpd-1.0.36 diff --git a/client.go b/client.go index e488d07..46a8c37 100644 --- a/client.go +++ b/client.go @@ -417,6 +417,24 @@ func (c *Client) openConn(idx int, host string) (pconn *persistentConn, err erro goto Error } + if pconn.hasFeature("CLNT") { + if err = pconn.setClient(); err != nil { + goto Error + } + } + + if pconn.hasFeature("UTF8") { + if err = pconn.setUnicode(); err != nil { + goto Error + } + } + + if pconn.hasFeature("MLST") { + if err = pconn.setMLST(); err != nil { + goto Error + } + } + c.mu.Lock() defer c.mu.Unlock() diff --git a/file_system.go b/file_system.go index d80bf40..9cd3cae 100644 --- a/file_system.go +++ b/file_system.go @@ -112,6 +112,27 @@ func (c *Client) Getwd() (string, error) { return dir, nil } +// Setwd changes the current working directory. +func (c *Client) Setwd(path string) (error) { + pconn, err := c.getIdleConn() + if err != nil { + return err + } + + defer c.returnConn(pconn) + + code, msg, err := pconn.sendCommand("CWD %s", path) + if err != nil { + return err + } + + if code != replyFileActionOkay { + return ftpError{code: code, msg: msg} + } + + return nil +} + func commandNotSupporterdError(err error) bool { respCode := err.(ftpError).Code() return respCode == replyCommandSyntaxError || respCode == replyCommandNotImplemented @@ -125,16 +146,27 @@ func commandNotSupporterdError(err error) bool { // be used. You may have to set ServerLocation in your config to get (more) // accurate ModTimes in this case. func (c *Client) ReadDir(path string) ([]os.FileInfo, error) { - entries, err := c.dataStringList("MLSD %s", path) + pconn, err := c.getIdleConn() + if err != nil { + return nil, err + } + defer c.returnConn(pconn) + + var entries []string + + if !pconn.mlsdNotSupported { + entries, err = c.dataStringList(pconn, "MLSD %s", path) + } parser := parseMLST - if err != nil { - if !commandNotSupporterdError(err) { + if pconn.mlsdNotSupported || err != nil { + if !pconn.mlsdNotSupported && !commandNotSupporterdError(err) { return nil, err } + pconn.mlsdNotSupported = true - entries, err = c.dataStringList("LIST %s", path) + entries, err = c.dataStringList(pconn, "LIST %s", path) if err != nil { return nil, err } @@ -148,7 +180,9 @@ func (c *Client) ReadDir(path string) ([]os.FileInfo, error) { info, err := parser(entry, true) if err != nil { c.debug("error in ReadDir: %s", err) - return nil, err + if !strings.Contains(err.Error(), "incomplete") { + return nil, err + } } if info == nil { @@ -167,10 +201,17 @@ func (c *Client) ReadDir(path string) ([]os.FileInfo, error) { // is a directory. You may have to set ServerLocation in your config to get // (more) accurate ModTimes when using "LIST". func (c *Client) Stat(path string) (os.FileInfo, error) { - lines, err := c.controlStringList("MLST %s", path) + pconn, err := c.getIdleConn() + if err != nil { + return nil, err + } + + defer c.returnConn(pconn) + + lines, err := c.controlStringList(pconn, "MLST %s", path) if err != nil { if commandNotSupporterdError(err) { - lines, err = c.dataStringList("LIST %s", path) + lines, err = c.dataStringList(pconn, "LIST %s", path) if err != nil { return nil, err } @@ -202,17 +243,10 @@ func extractDirName(msg string) (string, error) { return strings.Replace(msg[openQuote+1:closeQuote], `""`, `"`, -1), nil } -func (c *Client) controlStringList(f string, args ...interface{}) ([]string, error) { - pconn, err := c.getIdleConn() - if err != nil { - return nil, err - } - - defer c.returnConn(pconn) - +func (c *Client) controlStringList(pconn *persistentConn, f string, args ...interface{}) ([]string, error) { cmd := fmt.Sprintf(f, args...) - code, msg, err := pconn.sendCommand(cmd) + code, msg, _ := pconn.sendCommand(cmd) if !positiveCompletionReply(code) { pconn.debug("unexpected response to %s: %d-%s", cmd, code, msg) @@ -222,14 +256,7 @@ func (c *Client) controlStringList(f string, args ...interface{}) ([]string, err return strings.Split(msg, "\n"), nil } -func (c *Client) dataStringList(f string, args ...interface{}) ([]string, error) { - pconn, err := c.getIdleConn() - if err != nil { - return nil, err - } - - defer c.returnConn(pconn) - +func (c *Client) dataStringList(pconn *persistentConn, f string, args ...interface{}) ([]string, error) { dcGetter, err := pconn.prepareDataConn() if err != nil { return nil, err @@ -321,6 +348,66 @@ func (f *ftpFile) Sys() interface{} { return f.raw } +var dirRegex = regexp.MustCompile(`^\s*(\S+\s+\S+\s{0,1}\S{2})\s+(\S+)\s+(.*)`) +var dirTimeFormats = []string{ + "01-02-06 03:04", + "01-02-06 03:04PM", + "2006-01-02 15:04", +} + +// 08/03/2016 17:13