Update vendored deps, including AWS SDK, openpgp, ftp, ...

This commit is contained in:
Andrey Smirnov
2018-04-05 17:46:45 +03:00
parent cef4fefc40
commit 0e6ee35942
1497 changed files with 450721 additions and 68034 deletions
+4 -1
View File
@@ -2,7 +2,8 @@ language: go
dist: trusty
sudo: required
go:
- 1.7.3
- 1.9.x
- 1.10.x
env:
- FTP_SERVER=vsftpd
- FTP_SERVER=proftpd
@@ -10,5 +11,7 @@ before_install:
- sudo $TRAVIS_BUILD_DIR/.travis/prepare.sh "$FTP_SERVER"
- sudo sysctl net.ipv6.conf.lo.disable_ipv6=0
- go get github.com/mattn/goveralls
- go get github.com/golang/lint/golint
script:
- goveralls -v
- golint -set_exit_status $(go list ./...)
+207
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"io/ioutil"
"net/textproto"
"strings"
"testing"
"time"
)
@@ -67,6 +68,7 @@ func testConn(t *testing.T, disableEPSV bool) {
t.Error(err)
}
// Read without deadline
r, err := c.Retr("tset")
if err != nil {
t.Error(err)
@@ -79,8 +81,25 @@ func testConn(t *testing.T, disableEPSV bool) {
t.Errorf("'%s'", buf)
}
r.Close()
r.Close() // test we can close two times
}
// Read with deadline
r, err = c.Retr("tset")
if err != nil {
t.Error(err)
} else {
r.SetDeadline(time.Now())
_, err := ioutil.ReadAll(r)
if err == nil {
t.Error("deadline should have caused error")
} else if !strings.HasSuffix(err.Error(), "i/o timeout") {
t.Error(err)
}
r.Close()
}
// Read with offset
r, err = c.RetrFrom("tset", 5)
if err != nil {
t.Error(err)
@@ -249,3 +268,191 @@ func TestWrongLogin(t *testing.T) {
t.Fatal("expected error, got nil")
}
}
func TestDeleteDirRecur(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
c, err := DialTimeout("localhost:21", 5*time.Second)
if err != nil {
t.Fatal(err)
}
err = c.Login("anonymous", "anonymous")
if err != nil {
t.Fatal(err)
}
err = c.NoOp()
if err != nil {
t.Error(err)
}
err = c.ChangeDir("incoming")
if err != nil {
t.Error(err)
}
err = c.MakeDir("testDir")
if err != nil {
t.Error(err)
}
err = c.ChangeDir("testDir")
if err != nil {
t.Error(err)
}
err = c.MakeDir("anotherDir")
if err != nil {
t.Error(err)
}
data := bytes.NewBufferString("test text")
err = c.Stor("fileTest", data)
if err != nil {
t.Error(err)
}
err = c.ChangeDirToParent()
if err != nil {
t.Error(err)
}
err = c.RemoveDirRecur("testDir")
if err != nil {
t.Error(err)
}
dir, err := c.CurrentDir()
if err != nil {
t.Error(err)
} else {
if dir != "/incoming" {
t.Error("Wrong dir: " + dir)
}
}
err = c.ChangeDir("testDir")
if err == nil {
t.Fatal("expected error, got nil")
}
err = c.Logout()
if err != nil {
if protoErr := err.(*textproto.Error); protoErr != nil {
if protoErr.Code != StatusNotImplemented {
t.Error(err)
}
} else {
t.Error(err)
}
}
c.Quit()
}
func TestFileDeleteDirRecur(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
c, err := DialTimeout("localhost:21", 5*time.Second)
if err != nil {
t.Fatal(err)
}
err = c.Login("anonymous", "anonymous")
if err != nil {
t.Fatal(err)
}
err = c.ChangeDir("incoming")
if err != nil {
t.Error(err)
}
data := bytes.NewBufferString(testData)
err = c.Stor("testFile", data)
if err != nil {
t.Error(err)
}
err = c.RemoveDirRecur("testFile")
if err == nil {
t.Fatal("expected error got nill")
}
dir, err := c.CurrentDir()
if err != nil {
t.Error(err)
} else {
if dir != "/incoming" {
t.Error("Wrong dir: " + dir)
}
}
err = c.Delete("testFile")
if err != nil {
t.Error(err)
}
err = c.Logout()
if err != nil {
if protoErr := err.(*textproto.Error); protoErr != nil {
if protoErr.Code != StatusNotImplemented {
t.Error(err)
}
} else {
t.Error(err)
}
}
c.Quit()
}
func TestMissingFolderDeleteDirRecur(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
c, err := DialTimeout("localhost:21", 5*time.Second)
if err != nil {
t.Fatal(err)
}
err = c.Login("anonymous", "anonymous")
if err != nil {
t.Fatal(err)
}
err = c.ChangeDir("incoming")
if err != nil {
t.Error(err)
}
err = c.RemoveDirRecur("test")
if err == nil {
t.Fatal("expected error got nill")
}
dir, err := c.CurrentDir()
if err != nil {
t.Error(err)
} else {
if dir != "/incoming" {
t.Error("Wrong dir: " + dir)
}
}
err = c.Logout()
if err != nil {
if protoErr := err.(*textproto.Error); protoErr != nil {
if protoErr.Code != StatusNotImplemented {
t.Error(err)
}
} else {
t.Error(err)
}
}
c.Quit()
}
+83 -33
View File
@@ -30,6 +30,9 @@ type ServerConn struct {
// Do not use EPSV mode
DisableEPSV bool
// Timezone that the server is in
Location *time.Location
conn *textproto.Conn
host string
timeout time.Duration
@@ -45,10 +48,11 @@ type Entry struct {
Time time.Time
}
// response represent a data-connection
type response struct {
conn net.Conn
c *ServerConn
// Response represents a data-connection
type Response struct {
conn net.Conn
c *ServerConn
closed bool
}
// Connect is an alias to Dial, for backward compatibility
@@ -73,19 +77,16 @@ func DialTimeout(addr string, timeout time.Duration) (*ServerConn, error) {
// Use the resolved IP address in case addr contains a domain name
// If we use the domain name, we might not resolve to the same IP.
remoteAddr := tconn.RemoteAddr().String()
host, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
return nil, err
}
remoteAddr := tconn.RemoteAddr().(*net.TCPAddr)
conn := textproto.NewConn(tconn)
c := &ServerConn{
conn: conn,
host: host,
host: remoteAddr.IP.String(),
timeout: timeout,
features: make(map[string]string),
Location: time.UTC,
}
_, _, err = c.conn.ReadResponse(StatusReady)
@@ -134,11 +135,9 @@ func (c *ServerConn) Login(user, password string) error {
}
// Switch to UTF-8
if err := c.setUTF8(); err != nil {
return err
}
err = c.setUTF8()
return nil
return err
}
// feat issues a FEAT FTP command to list the additional commands supported by
@@ -221,7 +220,7 @@ func (c *ServerConn) epsv() (port int, err error) {
}
// pasv issues a "PASV" command to get a port number for a data connection.
func (c *ServerConn) pasv() (port int, err error) {
func (c *ServerConn) pasv() (host string, port int, err error) {
_, line, err := c.cmd(StatusPassiveMode, "PASV")
if err != nil {
return
@@ -231,14 +230,16 @@ func (c *ServerConn) pasv() (port int, err error) {
start := strings.Index(line, "(")
end := strings.LastIndex(line, ")")
if start == -1 || end == -1 {
return 0, errors.New("Invalid PASV response format")
err = errors.New("Invalid PASV response format")
return
}
// We have to split the response string
pasvData := strings.Split(line[start+1:end], ",")
if len(pasvData) < 6 {
return 0, errors.New("Invalid PASV response format")
err = errors.New("Invalid PASV response format")
return
}
// Let's compute the port number
@@ -256,15 +257,18 @@ func (c *ServerConn) pasv() (port int, err error) {
// Recompose port
port = portPart1*256 + portPart2
// Make the IP address to connect to
host = strings.Join(pasvData[0:4], ".")
return
}
// getDataConnPort returns a port for a new data connection
// getDataConnPort returns a host, port for a new data connection
// it uses the best available method to do so
func (c *ServerConn) getDataConnPort() (int, error) {
func (c *ServerConn) getDataConnPort() (string, int, error) {
if !c.DisableEPSV {
if port, err := c.epsv(); err == nil {
return port, nil
return c.host, port, nil
}
// if there is an error, disable EPSV for the next attempts
@@ -276,12 +280,12 @@ func (c *ServerConn) getDataConnPort() (int, error) {
// openDataConn creates a new FTP data connection.
func (c *ServerConn) openDataConn() (net.Conn, error) {
port, err := c.getDataConnPort()
host, port, err := c.getDataConnPort()
if err != nil {
return nil, err
}
return net.DialTimeout("tcp", net.JoinHostPort(c.host, strconv.Itoa(port)), c.timeout)
return net.DialTimeout("tcp", net.JoinHostPort(host, strconv.Itoa(port)), c.timeout)
}
// cmd is a helper function to execute a command and check for the expected FTP
@@ -337,7 +341,7 @@ func (c *ServerConn) NameList(path string) (entries []string, err error) {
return
}
r := &response{conn, c}
r := &Response{conn: conn, c: c}
defer r.Close()
scanner := bufio.NewScanner(r)
@@ -353,14 +357,14 @@ func (c *ServerConn) NameList(path string) (entries []string, err error) {
// List issues a LIST FTP command.
func (c *ServerConn) List(path string) (entries []*Entry, err error) {
var cmd string
var parseFunc func(string) (*Entry, error)
var parser parseFunc
if c.mlstSupported {
cmd = "MLSD"
parseFunc = parseRFC3659ListLine
parser = parseRFC3659ListLine
} else {
cmd = "LIST"
parseFunc = parseListLine
parser = parseListLine
}
conn, err := c.cmdDataConnFrom(0, "%s %s", cmd, path)
@@ -368,12 +372,13 @@ func (c *ServerConn) List(path string) (entries []*Entry, err error) {
return
}
r := &response{conn, c}
r := &Response{conn: conn, c: c}
defer r.Close()
scanner := bufio.NewScanner(r)
now := time.Now()
for scanner.Scan() {
entry, err := parseFunc(scanner.Text())
entry, err := parser(scanner.Text(), now, c.Location)
if err == nil {
entries = append(entries, entry)
}
@@ -431,7 +436,7 @@ func (c *ServerConn) FileSize(path string) (int64, error) {
// FTP server.
//
// The returned ReadCloser must be closed to cleanup the FTP data connection.
func (c *ServerConn) Retr(path string) (io.ReadCloser, error) {
func (c *ServerConn) Retr(path string) (*Response, error) {
return c.RetrFrom(path, 0)
}
@@ -439,13 +444,13 @@ func (c *ServerConn) Retr(path string) (io.ReadCloser, error) {
// FTP server, the server will not send the offset first bytes of the file.
//
// The returned ReadCloser must be closed to cleanup the FTP data connection.
func (c *ServerConn) RetrFrom(path string, offset uint64) (io.ReadCloser, error) {
func (c *ServerConn) RetrFrom(path string, offset uint64) (*Response, error) {
conn, err := c.cmdDataConnFrom(offset, "RETR %s", path)
if err != nil {
return nil, err
}
return &response{conn, c}, nil
return &Response{conn: conn, c: c}, nil
}
// Stor issues a STOR FTP command to store a file to the remote FTP server.
@@ -495,6 +500,41 @@ func (c *ServerConn) Delete(path string) error {
return err
}
// RemoveDirRecur deletes a non-empty folder recursively using
// RemoveDir and Delete
func (c *ServerConn) RemoveDirRecur(path string) error {
err := c.ChangeDir(path)
if err != nil {
return err
}
currentDir, err := c.CurrentDir()
if err != nil {
return err
}
entries, err := c.List(currentDir)
for _, entry := range entries {
if entry.Name != ".." && entry.Name != "." {
if entry.Type == EntryTypeFolder {
err = c.RemoveDirRecur(currentDir + "/" + entry.Name)
if err != nil {
return err
}
} else {
err = c.Delete(entry.Name)
if err != nil {
return err
}
}
}
}
err = c.ChangeDirToParent()
if err != nil {
return err
}
err = c.RemoveDir(currentDir)
return err
}
// MakeDir issues a MKD FTP command to create the specified directory on the
// remote FTP server.
func (c *ServerConn) MakeDir(path string) error {
@@ -531,16 +571,26 @@ func (c *ServerConn) Quit() error {
}
// Read implements the io.Reader interface on a FTP data connection.
func (r *response) Read(buf []byte) (int, error) {
func (r *Response) Read(buf []byte) (int, error) {
return r.conn.Read(buf)
}
// Close implements the io.Closer interface on a FTP data connection.
func (r *response) Close() error {
// After the first call, Close will do nothing and return nil.
func (r *Response) Close() error {
if r.closed {
return nil
}
err := r.conn.Close()
_, _, err2 := r.c.conn.ReadResponse(StatusClosingDataConnection)
if err2 != nil {
err = err2
}
r.closed = true
return err
}
// SetDeadline sets the deadlines associated with the connection.
func (r *Response) SetDeadline(t time.Time) error {
return r.conn.SetDeadline(t)
}
+63 -21
View File
@@ -2,6 +2,7 @@ package ftp
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
@@ -9,10 +10,13 @@ import (
var errUnsupportedListLine = errors.New("Unsupported LIST line")
var listLineParsers = []func(line string) (*Entry, error){
type parseFunc func(string, time.Time, *time.Location) (*Entry, error)
var listLineParsers = []parseFunc{
parseRFC3659ListLine,
parseLsListLine,
parseDirListLine,
parseHostedFTPLine,
}
var dirTimeFormats = []string{
@@ -21,7 +25,7 @@ var dirTimeFormats = []string{
}
// parseRFC3659ListLine parses the style of directory line defined in RFC 3659.
func parseRFC3659ListLine(line string) (*Entry, error) {
func parseRFC3659ListLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
iSemicolon := strings.Index(line, ";")
iWhitespace := strings.Index(line, " ")
@@ -39,13 +43,13 @@ func parseRFC3659ListLine(line string) (*Entry, error) {
return nil, errUnsupportedListLine
}
key := field[:i]
key := strings.ToLower(field[:i])
value := field[i+1:]
switch key {
case "modify":
var err error
e.Time, err = time.Parse("20060102150405", value)
e.Time, err = time.ParseInLocation("20060102150405", value, loc)
if err != nil {
return nil, err
}
@@ -65,7 +69,7 @@ func parseRFC3659ListLine(line string) (*Entry, error) {
// parseLsListLine parses a directory line in a format based on the output of
// the UNIX ls command.
func parseLsListLine(line string) (*Entry, error) {
func parseLsListLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
// Has the first field a length of 10 bytes?
if strings.IndexByte(line, ' ') != 10 {
@@ -84,7 +88,7 @@ func parseLsListLine(line string) (*Entry, error) {
Type: EntryTypeFolder,
Name: scanner.Remaining(),
}
if err := e.setTime(fields[3:6]); err != nil {
if err := e.setTime(fields[3:6], now, loc); err != nil {
return nil, err
}
@@ -99,9 +103,9 @@ func parseLsListLine(line string) (*Entry, error) {
}
if err := e.setSize(fields[2]); err != nil {
return nil, err
return nil, errUnsupportedListLine
}
if err := e.setTime(fields[4:7]); err != nil {
if err := e.setTime(fields[4:7], now, loc); err != nil {
return nil, err
}
@@ -131,7 +135,7 @@ func parseLsListLine(line string) (*Entry, error) {
return nil, errors.New("Unknown entry type")
}
if err := e.setTime(fields[5:8]); err != nil {
if err := e.setTime(fields[5:8], now, loc); err != nil {
return nil, err
}
@@ -140,14 +144,14 @@ func parseLsListLine(line string) (*Entry, error) {
// parseDirListLine parses a directory line in a format based on the output of
// the MS-DOS DIR command.
func parseDirListLine(line string) (*Entry, error) {
func parseDirListLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
e := &Entry{}
var err error
// Try various time formats that DIR might use, and stop when one works.
for _, format := range dirTimeFormats {
if len(line) > len(format) {
e.Time, err = time.Parse(format, line[:len(format)])
e.Time, err = time.ParseInLocation(format, line[:len(format)], loc)
if err == nil {
line = line[len(format):]
break
@@ -180,11 +184,32 @@ func parseDirListLine(line string) (*Entry, error) {
return e, nil
}
// parseHostedFTPLine parses a directory line in the non-standard format used
// by hostedftp.com
// -r-------- 0 user group 65222236 Feb 24 00:39 UABlacklistingWeek8.csv
// (The link count is inexplicably 0)
func parseHostedFTPLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
// Has the first field a length of 10 bytes?
if strings.IndexByte(line, ' ') != 10 {
return nil, errUnsupportedListLine
}
scanner := newScanner(line)
fields := scanner.NextFields(2)
if len(fields) < 2 || fields[1] != "0" {
return nil, errUnsupportedListLine
}
// Set link count to 1 and attempt to parse as Unix.
return parseLsListLine(fields[0]+" 1 "+scanner.Remaining(), now, loc)
}
// parseListLine parses the various non-standard format returned by the LIST
// FTP command.
func parseListLine(line string) (*Entry, error) {
func parseListLine(line string, now time.Time, loc *time.Location) (*Entry, error) {
for _, f := range listLineParsers {
e, err := f(line)
e, err := f(line, now, loc)
if err != errUnsupportedListLine {
return e, err
}
@@ -197,17 +222,34 @@ func (e *Entry) setSize(str string) (err error) {
return
}
func (e *Entry) setTime(fields []string) (err error) {
var timeStr string
if strings.Contains(fields[2], ":") { // this year
thisYear, _, _ := time.Now().Date()
timeStr = fields[1] + " " + fields[0] + " " + strconv.Itoa(thisYear)[2:4] + " " + fields[2] + " GMT"
} else { // not this year
func (e *Entry) setTime(fields []string, now time.Time, loc *time.Location) (err error) {
if strings.Contains(fields[2], ":") { // contains time
thisYear, _, _ := now.Date()
timeStr := fmt.Sprintf("%s %s %d %s", fields[1], fields[0], thisYear, fields[2])
e.Time, err = time.ParseInLocation("_2 Jan 2006 15:04", timeStr, loc)
/*
On unix, `info ls` shows:
10.1.6 Formatting file timestamps
---------------------------------
A timestamp is considered to be “recent” if it is less than six
months old, and is not dated in the future. If a timestamp dated today
is not listed in recent form, the timestamp is in the future, which
means you probably have clock skew problems which may break programs
like make that rely on file timestamps.
*/
if !e.Time.Before(now.AddDate(0, 6, 0)) {
e.Time = e.Time.AddDate(-1, 0, 0)
}
} else { // only the date
if len(fields[2]) != 4 {
return errors.New("Invalid year format in time string")
}
timeStr = fields[1] + " " + fields[0] + " " + fields[2][2:4] + " 00:00 GMT"
timeStr := fmt.Sprintf("%s %s %s 00:00", fields[1], fields[0], fields[2])
e.Time, err = time.ParseInLocation("_2 Jan 2006 15:04", timeStr, loc)
}
e.Time, err = time.Parse("_2 Jan 06 15:04 MST", timeStr)
return
}
+88 -25
View File
@@ -1,11 +1,18 @@
package ftp
import (
"strings"
"testing"
"time"
)
var thisYear, _, _ = time.Now().Date()
var (
// now is the current time for all tests
now = newTime(2017, time.March, 10, 23, 00)
thisYear, _, _ = now.Date()
previousYear = thisYear - 1
)
type line struct {
line string
@@ -22,40 +29,45 @@ type unsupportedLine struct {
var listTests = []line{
// UNIX ls -l style
{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 pub", "pub", 0, EntryTypeFolder, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)},
{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 p u b", "p u b", 0, EntryTypeFolder, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)},
{"-rw-r--r-- 1 marketwired marketwired 12016 Mar 16 2016 2016031611G087802-001.newsml", "2016031611G087802-001.newsml", 12016, EntryTypeFile, time.Date(2016, time.March, 16, 0, 0, 0, 0, time.UTC)},
{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 pub", "pub", 0, EntryTypeFolder, newTime(2009, time.December, 2)},
{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 p u b", "p u b", 0, EntryTypeFolder, newTime(2009, time.December, 2)},
{"-rw-r--r-- 1 marketwired marketwired 12016 Mar 16 2016 2016031611G087802-001.newsml", "2016031611G087802-001.newsml", 12016, EntryTypeFile, newTime(2016, time.March, 16)},
{"-rwxr-xr-x 3 110 1002 1234567 Dec 02 2009 fileName", "fileName", 1234567, EntryTypeFile, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)},
{"lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin", "bin -> usr/bin", 0, EntryTypeLink, time.Date(thisYear, time.January, 25, 0, 17, 0, 0, time.UTC)},
{"-rwxr-xr-x 3 110 1002 1234567 Dec 02 2009 fileName", "fileName", 1234567, EntryTypeFile, newTime(2009, time.December, 2)},
{"lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin", "bin -> usr/bin", 0, EntryTypeLink, newTime(thisYear, time.January, 25, 0, 17)},
// Another ls style
{"drwxr-xr-x folder 0 Aug 15 05:49 !!!-Tipp des Haus!", "!!!-Tipp des Haus!", 0, EntryTypeFolder, time.Date(thisYear, time.August, 15, 5, 49, 0, 0, time.UTC)},
{"drwxrwxrwx folder 0 Aug 11 20:32 P0RN", "P0RN", 0, EntryTypeFolder, time.Date(thisYear, time.August, 11, 20, 32, 0, 0, time.UTC)},
{"-rw-r--r-- 0 18446744073709551615 18446744073709551615 Nov 16 2006 VIDEO_TS.VOB", "VIDEO_TS.VOB", 18446744073709551615, EntryTypeFile, time.Date(2006, time.November, 16, 0, 0, 0, 0, time.UTC)},
{"drwxr-xr-x folder 0 Aug 15 05:49 !!!-Tipp des Haus!", "!!!-Tipp des Haus!", 0, EntryTypeFolder, newTime(thisYear, time.August, 15, 5, 49)},
{"drwxrwxrwx folder 0 Aug 11 20:32 P0RN", "P0RN", 0, EntryTypeFolder, newTime(thisYear, time.August, 11, 20, 32)},
{"-rw-r--r-- 0 18446744073709551615 18446744073709551615 Nov 16 2006 VIDEO_TS.VOB", "VIDEO_TS.VOB", 18446744073709551615, EntryTypeFile, newTime(2006, time.November, 16)},
// Microsoft's FTP servers for Windows
{"---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z", "ls-lR.Z", 1803128, EntryTypeFile, time.Date(thisYear, time.July, 10, 10, 18, 0, 0, time.UTC)},
{"d--------- 1 owner group 0 May 9 19:45 Softlib", "Softlib", 0, EntryTypeFolder, time.Date(thisYear, time.May, 9, 19, 45, 0, 0, time.UTC)},
{"---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z", "ls-lR.Z", 1803128, EntryTypeFile, newTime(thisYear, time.July, 10, 10, 18)},
{"d--------- 1 owner group 0 Nov 9 19:45 Softlib", "Softlib", 0, EntryTypeFolder, newTime(previousYear, time.November, 9, 19, 45)},
// WFTPD for MSDOS
{"-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp", "message.ftp", 322, EntryTypeFile, time.Date(1996, time.August, 19, 0, 0, 0, 0, time.UTC)},
{"-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp", "message.ftp", 322, EntryTypeFile, newTime(1996, time.August, 19)},
// RFC3659 format: https://tools.ietf.org/html/rfc3659#section-7
{"modify=20150813224845;perm=fle;type=cdir;unique=119FBB87U4;UNIX.group=0;UNIX.mode=0755;UNIX.owner=0; .", ".", 0, EntryTypeFolder, time.Date(2015, time.August, 13, 22, 48, 45, 0, time.UTC)},
{"modify=20150813224845;perm=fle;type=pdir;unique=119FBB87U4;UNIX.group=0;UNIX.mode=0755;UNIX.owner=0; ..", "..", 0, EntryTypeFolder, time.Date(2015, time.August, 13, 22, 48, 45, 0, time.UTC)},
{"modify=20150806235817;perm=fle;type=dir;unique=1B20F360U4;UNIX.group=0;UNIX.mode=0755;UNIX.owner=0; movies", "movies", 0, EntryTypeFolder, time.Date(2015, time.August, 6, 23, 58, 17, 0, time.UTC)},
{"modify=20150814172949;perm=flcdmpe;type=dir;unique=85A0C168U4;UNIX.group=0;UNIX.mode=0777;UNIX.owner=0; _upload", "_upload", 0, EntryTypeFolder, time.Date(2015, time.August, 14, 17, 29, 49, 0, time.UTC)},
{"modify=20150813175250;perm=adfr;size=951;type=file;unique=119FBB87UE;UNIX.group=0;UNIX.mode=0644;UNIX.owner=0; welcome.msg", "welcome.msg", 951, EntryTypeFile, time.Date(2015, time.August, 13, 17, 52, 50, 0, time.UTC)},
{"modify=20150813224845;perm=fle;type=cdir;unique=119FBB87U4;UNIX.group=0;UNIX.mode=0755;UNIX.owner=0; .", ".", 0, EntryTypeFolder, newTime(2015, time.August, 13, 22, 48, 45)},
{"modify=20150813224845;perm=fle;type=pdir;unique=119FBB87U4;UNIX.group=0;UNIX.mode=0755;UNIX.owner=0; ..", "..", 0, EntryTypeFolder, newTime(2015, time.August, 13, 22, 48, 45)},
{"modify=20150806235817;perm=fle;type=dir;unique=1B20F360U4;UNIX.group=0;UNIX.mode=0755;UNIX.owner=0; movies", "movies", 0, EntryTypeFolder, newTime(2015, time.August, 6, 23, 58, 17)},
{"modify=20150814172949;perm=flcdmpe;type=dir;unique=85A0C168U4;UNIX.group=0;UNIX.mode=0777;UNIX.owner=0; _upload", "_upload", 0, EntryTypeFolder, newTime(2015, time.August, 14, 17, 29, 49)},
{"modify=20150813175250;perm=adfr;size=951;type=file;unique=119FBB87UE;UNIX.group=0;UNIX.mode=0644;UNIX.owner=0; welcome.msg", "welcome.msg", 951, EntryTypeFile, newTime(2015, time.August, 13, 17, 52, 50)},
// Format and types have first letter UpperCase
{"Modify=20150813175250;Perm=adfr;Size=951;Type=file;Unique=119FBB87UE;UNIX.group=0;UNIX.mode=0644;UNIX.owner=0; welcome.msg", "welcome.msg", 951, EntryTypeFile, newTime(2015, time.August, 13, 17, 52, 50)},
// DOS DIR command output
{"08-07-15 07:50PM 718 Post_PRR_20150901_1166_265118_13049.dat", "Post_PRR_20150901_1166_265118_13049.dat", 718, EntryTypeFile, time.Date(2015, time.August, 7, 19, 50, 0, 0, time.UTC)},
{"08-10-15 02:04PM <DIR> Billing", "Billing", 0, EntryTypeFolder, time.Date(2015, time.August, 10, 14, 4, 0, 0, time.UTC)},
{"08-07-15 07:50PM 718 Post_PRR_20150901_1166_265118_13049.dat", "Post_PRR_20150901_1166_265118_13049.dat", 718, EntryTypeFile, newTime(2015, time.August, 7, 19, 50)},
{"08-10-15 02:04PM <DIR> Billing", "Billing", 0, EntryTypeFolder, newTime(2015, time.August, 10, 14, 4)},
// dir and file names that contain multiple spaces
{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 spaces dir name", "spaces dir name", 0, EntryTypeFolder, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)},
{"-rwxr-xr-x 3 110 1002 1234567 Dec 02 2009 file name", "file name", 1234567, EntryTypeFile, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)},
{"-rwxr-xr-x 3 110 1002 1234567 Dec 02 2009 foo bar ", " foo bar ", 1234567, EntryTypeFile, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)},
{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 spaces dir name", "spaces dir name", 0, EntryTypeFolder, newTime(2009, time.December, 2)},
{"-rwxr-xr-x 3 110 1002 1234567 Dec 02 2009 file name", "file name", 1234567, EntryTypeFile, newTime(2009, time.December, 2)},
{"-rwxr-xr-x 3 110 1002 1234567 Dec 02 2009 foo bar ", " foo bar ", 1234567, EntryTypeFile, newTime(2009, time.December, 2)},
// Odd link count from hostedftp.com
{"-r-------- 0 user group 65222236 Feb 24 00:39 RegularFile", "RegularFile", 65222236, EntryTypeFile, newTime(thisYear, time.February, 24, 0, 39)},
}
// Not supported, we expect a specific error message
@@ -66,12 +78,13 @@ var listTestsFail = []unsupportedLine{
{"modify=20150806235817;invalid;UNIX.owner=0; movies", "Unsupported LIST line"},
{"Zrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin", "Unknown entry type"},
{"total 1", "Unsupported LIST line"},
{"000000000x ", "Unsupported LIST line"}, // see https://github.com/jlaffaye/ftp/issues/97
{"", "Unsupported LIST line"},
}
func TestParseValidListLine(t *testing.T) {
for _, lt := range listTests {
entry, err := parseListLine(lt.line)
entry, err := parseListLine(lt.line, now, time.UTC)
if err != nil {
t.Errorf("parseListLine(%v) returned err = %v", lt.line, err)
continue
@@ -85,7 +98,7 @@ func TestParseValidListLine(t *testing.T) {
if entry.Size != lt.size {
t.Errorf("parseListLine(%v).Size = %v, want %v", lt.line, entry.Size, lt.size)
}
if entry.Time.Unix() != lt.time.Unix() {
if !entry.Time.Equal(lt.time) {
t.Errorf("parseListLine(%v).Time = %v, want %v", lt.line, entry.Time, lt.time)
}
}
@@ -93,7 +106,7 @@ func TestParseValidListLine(t *testing.T) {
func TestParseUnsupportedListLine(t *testing.T) {
for _, lt := range listTestsFail {
_, err := parseListLine(lt.line)
_, err := parseListLine(lt.line, now, time.UTC)
if err == nil {
t.Errorf("parseListLine(%v) expected to fail", lt.line)
}
@@ -102,3 +115,53 @@ func TestParseUnsupportedListLine(t *testing.T) {
}
}
}
func TestSettime(t *testing.T) {
tests := []struct {
line string
expected time.Time
}{
// this year, in the past
{"Feb 10 23:00", newTime(thisYear, time.February, 10, 23)},
// this year, less than six months in the future
{"Sep 10 22:59", newTime(thisYear, time.September, 10, 22, 59)},
// previous year, otherwise it would be more than 6 months in the future
{"Sep 10 23:00", newTime(previousYear, time.September, 10, 23)},
// far in the future
{"Jan 23 2019", newTime(2019, time.January, 23)},
}
for _, test := range tests {
entry := &Entry{}
entry.setTime(strings.Fields(test.line), now, time.UTC)
if !entry.Time.Equal(test.expected) {
t.Errorf("setTime(%v).Time = %v, want %v", test.line, entry.Time, test.expected)
}
}
}
// newTime builds a UTC time from the given year, month, day, hour and minute
func newTime(year int, month time.Month, day int, hourMinSec ...int) time.Time {
var hour, min, sec int
switch len(hourMinSec) {
case 0:
// nothing
case 3:
sec = hourMinSec[2]
fallthrough
case 2:
min = hourMinSec[1]
fallthrough
case 1:
hour = hourMinSec[0]
default:
panic("too many arguments")
}
return time.Date(year, month, day, hour, min, sec, 0, time.UTC)
}