ftp.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. package ftp
  2. import (
  3. "bufio"
  4. "net"
  5. "os"
  6. "fmt"
  7. "strconv"
  8. "strings"
  9. )
  10. const (
  11. EntryTypeFile = iota
  12. EntryTypeFolder
  13. EntryTypeLink
  14. )
  15. type ServerCon struct {
  16. conn net.Conn
  17. bio *bufio.Reader
  18. }
  19. type Response struct {
  20. conn net.Conn
  21. c *ServerCon
  22. }
  23. type Entry struct {
  24. Name string
  25. EntryType int
  26. Size uint64
  27. }
  28. // Check if the last status code is equal to the given code
  29. // If it is the case, err is nil
  30. // Returns the status line for further processing
  31. func (c *ServerCon) checkStatus(expected int) (line string, err os.Error) {
  32. line, err = c.bio.ReadString('\n')
  33. if err != nil {
  34. return
  35. }
  36. code, err := strconv.Atoi(line[:3]) // A status is 3 digits
  37. if err != nil {
  38. return
  39. }
  40. if code != expected {
  41. err = os.NewError(fmt.Sprintf("%d %s", code, statusText[code]))
  42. return
  43. }
  44. return
  45. }
  46. // Like send() but with formating.
  47. func (c *ServerCon) sendf(str string, a ...interface{}) (os.Error) {
  48. return c.send([]byte(fmt.Sprintf(str, a...)))
  49. }
  50. // Send a raw command on the connection.
  51. func (c *ServerCon) send(data []byte) (os.Error) {
  52. _, err := c.conn.Write(data)
  53. return err
  54. }
  55. // Connect to a ftp server and returns a ServerCon handler.
  56. func Connect(host, user, password string) (*ServerCon, os.Error) {
  57. conn, err := net.Dial("tcp", host)
  58. if err != nil {
  59. return nil, err
  60. }
  61. c := &ServerCon{conn, bufio.NewReader(conn)}
  62. _, err = c.checkStatus(StatusReady)
  63. if err != nil {
  64. c.Close()
  65. return nil, err
  66. }
  67. c.sendf("USER %v\r\n", user)
  68. _, err = c.checkStatus(StatusUserOK)
  69. if err != nil {
  70. c.Close()
  71. return nil, err
  72. }
  73. c.sendf("PASS %v\r\n", password)
  74. _, err = c.checkStatus(StatusLoggedIn)
  75. if err != nil {
  76. c.Close()
  77. return nil, err
  78. }
  79. return c, nil
  80. }
  81. // Like Connect() but with anonymous credentials.
  82. func ConnectAnonymous(host string) (*ServerCon, os.Error) {
  83. return Connect(host, "anonymous", "anonymous")
  84. }
  85. // Enter extended passive mode
  86. func (c *ServerCon) epsv() (port int, err os.Error) {
  87. c.send([]byte("EPSV\r\n"))
  88. line, err := c.checkStatus(StatusExtendedPassiveMode)
  89. if err != nil {
  90. return
  91. }
  92. start := strings.Index(line, "|||")
  93. end := strings.LastIndex(line, "|")
  94. if start == -1 || end == -1 {
  95. err = os.NewError("Invalid EPSV response format")
  96. return
  97. }
  98. port, err = strconv.Atoi(line[start+3 : end])
  99. return
  100. }
  101. // Open a new data connection using extended passive mode
  102. func (c *ServerCon) openDataConnection() (r *Response, err os.Error) {
  103. port, err := c.epsv()
  104. if err != nil {
  105. return
  106. }
  107. // Build the new net address string
  108. a := strings.Split(c.conn.RemoteAddr().String(), ":", 2)
  109. addr := fmt.Sprintf("%v:%v", a[0], port)
  110. conn, err := net.Dial("tcp", addr)
  111. if err != nil {
  112. return
  113. }
  114. r = &Response{conn, c}
  115. return
  116. }
  117. func parseListLine(line string) (*Entry, os.Error) {
  118. fields := strings.Fields(line)
  119. if len(fields) < 9 {
  120. return nil, os.NewError("Unsupported LIST line")
  121. }
  122. e := &Entry{}
  123. switch fields[0][0] {
  124. case '-':
  125. e.EntryType = EntryTypeFile
  126. case 'd':
  127. e.EntryType = EntryTypeFolder
  128. case 'l':
  129. e.EntryType = EntryTypeLink
  130. default:
  131. return nil, os.NewError("Unknown entry type")
  132. }
  133. e.Name = strings.Join(fields[8:], " ")
  134. return e, nil
  135. }
  136. func (c *ServerCon) List() (entries []*Entry, err os.Error) {
  137. r, err := c.openDataConnection()
  138. if err != nil {
  139. return
  140. }
  141. defer r.Close()
  142. c.send([]byte("LIST\r\n"))
  143. _, err = c.checkStatus(StatusAboutToSend)
  144. if err != nil {
  145. return
  146. }
  147. bio := bufio.NewReader(r)
  148. for {
  149. line, e := bio.ReadString('\n')
  150. if e == os.EOF {
  151. break
  152. } else if e != nil {
  153. return nil, e
  154. }
  155. entry, err := parseListLine(line)
  156. if err == nil {
  157. entries = append(entries, entry)
  158. }
  159. }
  160. return
  161. }
  162. func (c *ServerCon) ChangeDir(path string) (err os.Error) {
  163. c.sendf("CWD %s\r\n", path);
  164. _, err = c.checkStatus(StatusRequestedFileActionOK)
  165. return
  166. }
  167. func (c *ServerCon) Get(path string) (r *Response, err os.Error) {
  168. r, err = c.openDataConnection()
  169. if err != nil {
  170. return
  171. }
  172. c.sendf("RETR %s\r\n", path)
  173. _, err = c.checkStatus(StatusAboutToSend)
  174. return
  175. }
  176. func (c *ServerCon) Close() {
  177. c.send([]byte("QUIT\r\n"))
  178. c.conn.Close()
  179. }
  180. func (r *Response) Read(buf []byte) (int, os.Error) {
  181. n, err := r.conn.Read(buf)
  182. if err == os.EOF {
  183. _, err2 := r.c.checkStatus(StatusClosingDataConnection)
  184. if err2 != nil {
  185. err = err2
  186. }
  187. }
  188. return n, err
  189. }
  190. func (r *Response) Close() os.Error {
  191. return r.conn.Close()
  192. }