Fix time parsing and reduce syscalls

closes #109
This commit is contained in:
Julian Kornberger 2018-01-04 13:03:09 +01:00
parent c2fc7f0a5a
commit 352a1d8821
3 changed files with 80 additions and 28 deletions

9
ftp.go
View File

@ -350,14 +350,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)
@ -369,8 +369,9 @@ func (c *ServerConn) List(path string) (entries []*Entry, err error) {
defer r.Close()
scanner := bufio.NewScanner(r)
now := time.Now()
for scanner.Scan() {
entry, err := parseFunc(scanner.Text())
entry, err := parser(scanner.Text(), now)
if err == nil {
entries = append(entries, entry)
}

View File

@ -9,7 +9,9 @@ import (
var errUnsupportedListLine = errors.New("Unsupported LIST line")
var listLineParsers = []func(line string) (*Entry, error){
type parseFunc func(string, time.Time) (*Entry, error)
var listLineParsers = []parseFunc{
parseRFC3659ListLine,
parseLsListLine,
parseDirListLine,
@ -22,7 +24,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) (*Entry, error) {
iSemicolon := strings.Index(line, ";")
iWhitespace := strings.Index(line, " ")
@ -66,7 +68,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) (*Entry, error) {
// Has the first field a length of 10 bytes?
if strings.IndexByte(line, ' ') != 10 {
@ -85,7 +87,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); err != nil {
return nil, err
}
@ -102,7 +104,7 @@ func parseLsListLine(line string) (*Entry, error) {
if err := e.setSize(fields[2]); err != nil {
return nil, errUnsupportedListLine
}
if err := e.setTime(fields[4:7]); err != nil {
if err := e.setTime(fields[4:7], now); err != nil {
return nil, err
}
@ -132,7 +134,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); err != nil {
return nil, err
}
@ -141,7 +143,7 @@ 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) (*Entry, error) {
e := &Entry{}
var err error
@ -185,7 +187,7 @@ func parseDirListLine(line string) (*Entry, error) {
// by hostedftp.com
// -r-------- 0 user group 65222236 Feb 24 00:39 UABlacklistingWeek8.csv
// (The link count is inexplicably 0)
func parseHostedFTPLine(line string) (*Entry, error) {
func parseHostedFTPLine(line string, now time.Time) (*Entry, error) {
// Has the first field a length of 10 bytes?
if strings.IndexByte(line, ' ') != 10 {
return nil, errUnsupportedListLine
@ -199,14 +201,14 @@ func parseHostedFTPLine(line string) (*Entry, error) {
}
// Set link count to 1 and attempt to parse as Unix.
return parseLsListLine(fields[0] + " 1 " + scanner.Remaining())
return parseLsListLine(fields[0]+" 1 "+scanner.Remaining(), now)
}
// 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) (*Entry, error) {
for _, f := range listLineParsers {
e, err := f(line)
e, err := f(line, now)
if err != errUnsupportedListLine {
return e, err
}
@ -219,17 +221,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) (err error) {
if strings.Contains(fields[2], ":") { // contains time
thisYear, _, _ := now.Date()
timeStr := fields[1] + " " + fields[0] + " " + strconv.Itoa(thisYear)[2:4] + " " + fields[2] + " GMT"
e.Time, err = time.Parse("_2 Jan 06 15:04 MST", timeStr)
/*
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 := fields[1] + " " + fields[0] + " " + fields[2][2:4] + " 00:00 GMT"
e.Time, err = time.Parse("_2 Jan 06 15:04 MST", timeStr)
}
e.Time, err = time.Parse("_2 Jan 06 15:04 MST", timeStr)
return
}

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 = time.Date(2017, time.March, 10, 23, 0, 0, 0, time.UTC)
thisYear, _, _ = now.Date()
previousYear = thisYear - 1
)
type line struct {
line string
@ -36,7 +43,7 @@ var listTests = []line{
// 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)},
{"d--------- 1 owner group 0 Nov 9 19:45 Softlib", "Softlib", 0, EntryTypeFolder, time.Date(previousYear, time.November, 9, 19, 45, 0, 0, time.UTC)},
// 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)},
@ -77,7 +84,7 @@ var listTestsFail = []unsupportedLine{
func TestParseValidListLine(t *testing.T) {
for _, lt := range listTests {
entry, err := parseListLine(lt.line)
entry, err := parseListLine(lt.line, now)
if err != nil {
t.Errorf("parseListLine(%v) returned err = %v", lt.line, err)
continue
@ -91,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)
}
}
@ -99,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)
if err == nil {
t.Errorf("parseListLine(%v) expected to fail", lt.line)
}
@ -108,3 +115,28 @@ 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", time.Date(thisYear, time.February, 10, 23, 0, 0, 0, time.UTC)},
// this year, less than six months in the future
{"Sep 10 22:59", time.Date(thisYear, time.September, 10, 22, 59, 0, 0, time.UTC)},
// previous year, otherwise it would be more than 6 months in the future
{"Sep 10 23:00", time.Date(previousYear, time.September, 10, 23, 0, 0, 0, time.UTC)},
}
for _, test := range tests {
entry := &Entry{}
entry.setTime(strings.Fields(test.line), now)
if !entry.Time.Equal(test.expected) {
t.Errorf("setTime(%v).Time = %v, want %v", test.line, entry.Time, test.expected)
}
}
}