Add support for directory listings in MS-DOS DIR format.
This commit is contained in:
		
							parent
							
								
									7acded32b2
								
							
						
					
					
						commit
						95346071de
					
				
							
								
								
									
										239
									
								
								ftp.go
									
									
									
									
									
								
							
							
						
						
									
										239
									
								
								ftp.go
									
									
									
									
									
								
							@ -286,98 +286,169 @@ func (c *ServerConn) cmdDataConnFrom(offset uint64, format string, args ...inter
 | 
				
			|||||||
	return conn, nil
 | 
						return conn, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// parseListLine parses the various non-standard format returned by the LIST
 | 
					var errUnsupportedListLine = errors.New("Unsupported LIST line")
 | 
				
			||||||
// FTP command.
 | 
					 | 
				
			||||||
func parseListLine(line string) (*Entry, error) {
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// RFC3659 style
 | 
					// parseRFC3659ListLine parses the style of directory line defined in RFC 3659.
 | 
				
			||||||
	if i := strings.Index(line, ";"); i > 0 && i < strings.Index(line, " ") {
 | 
					func parseRFC3659ListLine(line string) (*Entry, error) {
 | 
				
			||||||
		e := &Entry{}
 | 
						if i := strings.Index(line, ";"); i < 0 || i > strings.Index(line, " ") {
 | 
				
			||||||
		arr := strings.Split(line, "; ")
 | 
							return nil, errUnsupportedListLine
 | 
				
			||||||
		e.Name = arr[1]
 | 
						}
 | 
				
			||||||
 | 
						e := &Entry{}
 | 
				
			||||||
 | 
						arr := strings.Split(line, "; ")
 | 
				
			||||||
 | 
						e.Name = arr[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for _, field := range strings.Split(arr[0], ";") {
 | 
						for _, field := range strings.Split(arr[0], ";") {
 | 
				
			||||||
			i := strings.Index(field, "=")
 | 
							i := strings.Index(field, "=")
 | 
				
			||||||
			if i < 1 {
 | 
							if i < 1 {
 | 
				
			||||||
				return nil, errors.New("Unsupported LIST line")
 | 
								return nil, errUnsupportedListLine
 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			key := field[:i]
 | 
					 | 
				
			||||||
			value := field[i+1:]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			switch key {
 | 
					 | 
				
			||||||
			case "modify":
 | 
					 | 
				
			||||||
				e.Time, _ = time.Parse("20060102150405", value)
 | 
					 | 
				
			||||||
			case "type":
 | 
					 | 
				
			||||||
				switch value {
 | 
					 | 
				
			||||||
				case "dir", "cdir", "pdir":
 | 
					 | 
				
			||||||
					e.Type = EntryTypeFolder
 | 
					 | 
				
			||||||
				case "file":
 | 
					 | 
				
			||||||
					e.Type = EntryTypeFile
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			case "size":
 | 
					 | 
				
			||||||
				e.setSize(value)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return e, nil
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	} else {
 | 
							key := field[:i]
 | 
				
			||||||
		fields := strings.Fields(line)
 | 
							value := field[i+1:]
 | 
				
			||||||
		if len(fields) >= 7 && fields[1] == "folder" && fields[2] == "0" {
 | 
					
 | 
				
			||||||
			e := &Entry{
 | 
							switch key {
 | 
				
			||||||
				Type: EntryTypeFolder,
 | 
							case "modify":
 | 
				
			||||||
				Name: strings.Join(fields[6:], " "),
 | 
								var err error
 | 
				
			||||||
			}
 | 
								e.Time, err = time.Parse("20060102150405", value)
 | 
				
			||||||
			if err = e.setTime(fields[3:6]); err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return nil, err
 | 
									return nil, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							case "type":
 | 
				
			||||||
			return e, nil
 | 
								switch value {
 | 
				
			||||||
		}
 | 
								case "dir", "cdir", "pdir":
 | 
				
			||||||
 | 
									e.Type = EntryTypeFolder
 | 
				
			||||||
		if fields[1] == "0" {
 | 
								case "file":
 | 
				
			||||||
			e := &Entry{
 | 
									e.Type = EntryTypeFile
 | 
				
			||||||
				Type: EntryTypeFile,
 | 
					 | 
				
			||||||
				Name: strings.Join(fields[7:], " "),
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							case "size":
 | 
				
			||||||
			if err = e.setSize(fields[2]); err != nil {
 | 
								e.setSize(value)
 | 
				
			||||||
				return nil, err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if err = e.setTime(fields[4:7]); err != nil {
 | 
					 | 
				
			||||||
				return nil, err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return e, nil
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return e, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if len(fields) < 9 {
 | 
					// parseLsListLine parses a directory line in a format based on the output of
 | 
				
			||||||
			return nil, errors.New("Unsupported LIST line")
 | 
					// 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 {
 | 
				
			||||||
		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
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		e.Name = strings.Join(fields[8:], " ")
 | 
					 | 
				
			||||||
		return e, nil
 | 
							return e, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 = 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 {
 | 
				
			||||||
 | 
							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) {
 | 
					func (e *Entry) setSize(str string) (err error) {
 | 
				
			||||||
@ -430,19 +501,17 @@ func (c *ServerConn) List(path string) (entries []*Entry, err error) {
 | 
				
			|||||||
	r := &response{conn, c}
 | 
						r := &response{conn, c}
 | 
				
			||||||
	defer r.Close()
 | 
						defer r.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bio := bufio.NewReader(r)
 | 
						scanner := bufio.NewScanner(r)
 | 
				
			||||||
	for {
 | 
						for scanner.Scan() {
 | 
				
			||||||
		line, e := bio.ReadString('\n')
 | 
							line := scanner.Text()
 | 
				
			||||||
		if e == io.EOF {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		} else if e != nil {
 | 
					 | 
				
			||||||
			return nil, e
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		entry, err := parseListLine(line)
 | 
							entry, err := parseListLine(line)
 | 
				
			||||||
		if err == nil {
 | 
							if err == nil {
 | 
				
			||||||
			entries = append(entries, entry)
 | 
								entries = append(entries, entry)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if err := scanner.Err(); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,10 @@ var listTests = []line{
 | 
				
			|||||||
	{"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=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=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=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)},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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)},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Not supported, we expect a specific error message
 | 
					// Not supported, we expect a specific error message
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user