parse.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. package ftp
  2. import (
  3. "errors"
  4. "strconv"
  5. "strings"
  6. "time"
  7. )
  8. var errUnsupportedListLine = errors.New("Unsupported LIST line")
  9. var listLineParsers = []func(line string) (*Entry, error){
  10. parseRFC3659ListLine,
  11. parseLsListLine,
  12. parseDirListLine,
  13. }
  14. var dirTimeFormats = []string{
  15. "01-02-06 03:04PM",
  16. "2006-01-02 15:04",
  17. }
  18. // parseRFC3659ListLine parses the style of directory line defined in RFC 3659.
  19. func parseRFC3659ListLine(line string) (*Entry, error) {
  20. iSemicolon := strings.Index(line, ";")
  21. iWhitespace := strings.Index(line, " ")
  22. if iSemicolon < 0 || iSemicolon > iWhitespace {
  23. return nil, errUnsupportedListLine
  24. }
  25. e := &Entry{
  26. Name: line[iWhitespace+1:],
  27. }
  28. for _, field := range strings.Split(line[:iWhitespace-1], ";") {
  29. i := strings.Index(field, "=")
  30. if i < 1 {
  31. return nil, errUnsupportedListLine
  32. }
  33. key := field[:i]
  34. value := field[i+1:]
  35. switch key {
  36. case "modify":
  37. var err error
  38. e.Time, err = time.Parse("20060102150405", value)
  39. if err != nil {
  40. return nil, err
  41. }
  42. case "type":
  43. switch value {
  44. case "dir", "cdir", "pdir":
  45. e.Type = EntryTypeFolder
  46. case "file":
  47. e.Type = EntryTypeFile
  48. }
  49. case "size":
  50. e.setSize(value)
  51. }
  52. }
  53. return e, nil
  54. }
  55. // parseLsListLine parses a directory line in a format based on the output of
  56. // the UNIX ls command.
  57. func parseLsListLine(line string) (*Entry, error) {
  58. // Has the first field a length of 10 bytes?
  59. if strings.IndexByte(line, ' ') != 10 {
  60. return nil, errUnsupportedListLine
  61. }
  62. scanner := newScanner(line)
  63. fields := scanner.NextFields(6)
  64. if len(fields) < 6 {
  65. return nil, errUnsupportedListLine
  66. }
  67. if fields[1] == "folder" && fields[2] == "0" {
  68. e := &Entry{
  69. Type: EntryTypeFolder,
  70. Name: scanner.Remaining(),
  71. }
  72. if err := e.setTime(fields[3:6]); err != nil {
  73. return nil, err
  74. }
  75. return e, nil
  76. }
  77. if fields[1] == "0" {
  78. fields = append(fields, scanner.Next())
  79. e := &Entry{
  80. Type: EntryTypeFile,
  81. Name: scanner.Remaining(),
  82. }
  83. if err := e.setSize(fields[2]); err != nil {
  84. return nil, err
  85. }
  86. if err := e.setTime(fields[4:7]); err != nil {
  87. return nil, err
  88. }
  89. return e, nil
  90. }
  91. // Read two more fields
  92. fields = append(fields, scanner.NextFields(2)...)
  93. if len(fields) < 8 {
  94. return nil, errUnsupportedListLine
  95. }
  96. e := &Entry{
  97. Name: scanner.Remaining(),
  98. }
  99. switch fields[0][0] {
  100. case '-':
  101. e.Type = EntryTypeFile
  102. if err := e.setSize(fields[4]); err != nil {
  103. return nil, err
  104. }
  105. case 'd':
  106. e.Type = EntryTypeFolder
  107. case 'l':
  108. e.Type = EntryTypeLink
  109. default:
  110. return nil, errors.New("Unknown entry type")
  111. }
  112. if err := e.setTime(fields[5:8]); err != nil {
  113. return nil, err
  114. }
  115. return e, nil
  116. }
  117. // parseDirListLine parses a directory line in a format based on the output of
  118. // the MS-DOS DIR command.
  119. func parseDirListLine(line string) (*Entry, error) {
  120. e := &Entry{}
  121. var err error
  122. // Try various time formats that DIR might use, and stop when one works.
  123. for _, format := range dirTimeFormats {
  124. if len(line) > len(format) {
  125. e.Time, err = time.Parse(format, line[:len(format)])
  126. if err == nil {
  127. line = line[len(format):]
  128. break
  129. }
  130. }
  131. }
  132. if err != nil {
  133. // None of the time formats worked.
  134. return nil, errUnsupportedListLine
  135. }
  136. line = strings.TrimLeft(line, " ")
  137. if strings.HasPrefix(line, "<DIR>") {
  138. e.Type = EntryTypeFolder
  139. line = strings.TrimPrefix(line, "<DIR>")
  140. } else {
  141. space := strings.Index(line, " ")
  142. if space == -1 {
  143. return nil, errUnsupportedListLine
  144. }
  145. e.Size, err = strconv.ParseUint(line[:space], 10, 64)
  146. if err != nil {
  147. return nil, errUnsupportedListLine
  148. }
  149. e.Type = EntryTypeFile
  150. line = line[space:]
  151. }
  152. e.Name = strings.TrimLeft(line, " ")
  153. return e, nil
  154. }
  155. // parseListLine parses the various non-standard format returned by the LIST
  156. // FTP command.
  157. func parseListLine(line string) (*Entry, error) {
  158. for _, f := range listLineParsers {
  159. e, err := f(line)
  160. if err != errUnsupportedListLine {
  161. return e, err
  162. }
  163. }
  164. return nil, errUnsupportedListLine
  165. }
  166. func (e *Entry) setSize(str string) (err error) {
  167. e.Size, err = strconv.ParseUint(str, 0, 64)
  168. return
  169. }
  170. func (e *Entry) setTime(fields []string) (err error) {
  171. var timeStr string
  172. if strings.Contains(fields[2], ":") { // this year
  173. thisYear, _, _ := time.Now().Date()
  174. timeStr = fields[1] + " " + fields[0] + " " + strconv.Itoa(thisYear)[2:4] + " " + fields[2] + " GMT"
  175. } else { // not this year
  176. if len(fields[2]) != 4 {
  177. return errors.New("Invalid year format in time string")
  178. }
  179. timeStr = fields[1] + " " + fields[0] + " " + fields[2][2:4] + " 00:00 GMT"
  180. }
  181. e.Time, err = time.Parse("_2 Jan 06 15:04 MST", timeStr)
  182. return
  183. }