Skip to content

Commit

Permalink
Merge pull request #1 from jlaffaye/master
Browse files Browse the repository at this point in the history
Updates from upstream
  • Loading branch information
rafael84 authored Jan 23, 2017
2 parents 988909a + 5a8b8ee commit 946f885
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 215 deletions.
239 changes: 24 additions & 215 deletions ftp.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ func DialTimeout(addr string, timeout time.Duration) (*ServerConn, error) {
return nil, err
}

err = c.setUTF8()
if err != nil {
c.Quit()
return nil, err
}

return c, nil
}

Expand Down Expand Up @@ -164,6 +170,24 @@ func (c *ServerConn) feat() error {
return nil
}

// setUTF8 issues an "OPTS UTF8 ON" command.
func (c *ServerConn) setUTF8() error {
if _, ok := c.features["UTF8"]; !ok {
return nil
}

code, message, err := c.cmd(-1, "OPTS UTF8 ON")
if err != nil {
return err
}

if code != StatusCommandOK {
return errors.New(message)
}

return nil
}

// epsv issues an "EPSV" command to get a port number for a data connection.
func (c *ServerConn) epsv() (port int, err error) {
_, line, err := c.cmd(StatusExtendedPassiveMode, "EPSV")
Expand Down Expand Up @@ -291,221 +315,6 @@ func (c *ServerConn) cmdDataConnFrom(offset uint64, format string, args ...inter
return conn, nil
}

var errUnsupportedListLine = errors.New("Unsupported LIST line")

// parseRFC3659ListLine parses the style of directory line defined in RFC 3659.
func parseRFC3659ListLine(line string) (*Entry, error) {
iSemicolon := strings.Index(line, ";")
iWhitespace := strings.Index(line, " ")

if iSemicolon < 0 || iSemicolon > iWhitespace {
return nil, errUnsupportedListLine
}

e := &Entry{
Name: line[iWhitespace+1:],
}

for _, field := range strings.Split(line[:iWhitespace-1], ";") {
i := strings.Index(field, "=")
if i < 1 {
return nil, errUnsupportedListLine
}

key := field[:i]
value := field[i+1:]

switch key {
case "modify":
var err error
e.Time, err = time.Parse("20060102150405", value)
if err != nil {
return nil, err
}
case "type":
switch value {
case "dir", "cdir", "pdir":
e.Type = EntryTypeFolder
case "file":
e.Type = EntryTypeFile
}
case "size":
e.setSize(value)
}
}
return e, nil
}

// parse file or folder name with multiple spaces
func parseLsListLineName(line string, fields []string, offset int) string {
if offset < 1 {
return ""
}

match := fields[offset-1]
index := strings.Index(line, match)
if index == -1 {
return ""
}

index += len(match)
return strings.TrimSpace(line[index:])
}

// parseLsListLine parses a directory line in a format based on the output of
// the UNIX ls command.
func parseLsListLine(line string) (*Entry, error) {
fields := strings.Fields(line)
if len(fields) >= 7 && fields[1] == "folder" && fields[2] == "0" {
e := &Entry{
Type: EntryTypeFolder,
Name: strings.Join(fields[6:], " "),
}
if err := e.setTime(fields[3:6]); err != nil {
return nil, err
}

return e, nil
}

if len(fields) < 8 {
return nil, errUnsupportedListLine
}

if fields[1] == "0" {
e := &Entry{
Type: EntryTypeFile,
Name: strings.Join(fields[7:], " "),
}

if err := e.setSize(fields[2]); err != nil {
return nil, err
}
if err := e.setTime(fields[4:7]); err != nil {
return nil, err
}

return e, nil
}

if len(fields) < 9 {
return nil, errUnsupportedListLine
}

e := &Entry{}
switch fields[0][0] {
case '-':
e.Type = EntryTypeFile
if err := e.setSize(fields[4]); err != nil {
return nil, err
}
case 'd':
e.Type = EntryTypeFolder
case 'l':
e.Type = EntryTypeLink
default:
return nil, errors.New("Unknown entry type")
}

if err := e.setTime(fields[5:8]); err != nil {
return nil, err
}

e.Name = parseLsListLineName(line, fields, 8)
if len(e.Name) == 0 {
e.Name = strings.Join(fields[8:], " ")
}

return e, nil
}

var dirTimeFormats = []string{
"01-02-06 03:04PM",
"2006-01-02 15:04",
}

// parseDirListLine parses a directory line in a format based on the output of
// the MS-DOS DIR command.
func parseDirListLine(line string) (*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)])
if err == nil {
line = line[len(format):]
break
}
}
}
if err != nil {
// None of the time formats worked.
return nil, errUnsupportedListLine
}

line = strings.TrimLeft(line, " ")
if strings.HasPrefix(line, "<DIR>") {
e.Type = EntryTypeFolder
line = strings.TrimPrefix(line, "<DIR>")
} else {
space := strings.Index(line, " ")
if space == -1 {
return nil, errUnsupportedListLine
}
e.Size, err = strconv.ParseUint(line[:space], 10, 64)
if err != nil {
return nil, errUnsupportedListLine
}
e.Type = EntryTypeFile
line = line[space:]
}

e.Name = strings.TrimLeft(line, " ")
return e, nil
}

var listLineParsers = []func(line string) (*Entry, error){
parseRFC3659ListLine,
parseLsListLine,
parseDirListLine,
}

// parseListLine parses the various non-standard format returned by the LIST
// FTP command.
func parseListLine(line string) (*Entry, error) {
for _, f := range listLineParsers {
e, err := f(line)
if err == errUnsupportedListLine {
// Try another format.
continue
}
return e, err
}
return nil, errUnsupportedListLine
}

func (e *Entry) setSize(str string) (err error) {
e.Size, err = strconv.ParseUint(str, 0, 64)
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
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"
}
e.Time, err = time.Parse("_2 Jan 06 15:04 MST", timeStr)
return
}

// NameList issues an NLST FTP command.
func (c *ServerConn) NameList(path string) (entries []string, err error) {
conn, err := c.cmdDataConnFrom(0, "NLST %s", path)
Expand Down
Loading

0 comments on commit 946f885

Please sign in to comment.