conn_test.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package ftp
  2. import (
  3. "errors"
  4. "io"
  5. "io/ioutil"
  6. "net"
  7. "net/textproto"
  8. "reflect"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "testing"
  13. )
  14. type ftpMock struct {
  15. address string
  16. listener *net.TCPListener
  17. proto *textproto.Conn
  18. commands []string // list of received commands
  19. rest int
  20. dataConn *mockDataConn
  21. sync.WaitGroup
  22. }
  23. // newFtpMock returns a mock implementation of a FTP server
  24. // For simplication, a mock instance only accepts a signle connection and terminates afer
  25. func newFtpMock(t *testing.T, address string) (*ftpMock, error) {
  26. var err error
  27. mock := &ftpMock{address: address}
  28. l, err := net.Listen("tcp", address+":0")
  29. if err != nil {
  30. return nil, err
  31. }
  32. tcpListener, ok := l.(*net.TCPListener)
  33. if !ok {
  34. return nil, errors.New("listener is not a net.TCPListener")
  35. }
  36. mock.listener = tcpListener
  37. go mock.listen(t)
  38. return mock, nil
  39. }
  40. func (mock *ftpMock) listen(t *testing.T) {
  41. // Listen for an incoming connection.
  42. conn, err := mock.listener.Accept()
  43. if err != nil {
  44. t.Errorf("can not accept: %s", err)
  45. return
  46. }
  47. // Do not accept incoming connections anymore
  48. mock.listener.Close()
  49. mock.Add(1)
  50. defer mock.Done()
  51. defer conn.Close()
  52. mock.proto = textproto.NewConn(conn)
  53. mock.proto.Writer.PrintfLine("220 FTP Server ready.")
  54. for {
  55. fullCommand, _ := mock.proto.ReadLine()
  56. cmdParts := strings.Split(fullCommand, " ")
  57. // Append to list of received commands
  58. mock.commands = append(mock.commands, cmdParts[0])
  59. // At least one command must have a multiline response
  60. switch cmdParts[0] {
  61. case "FEAT":
  62. mock.proto.Writer.PrintfLine("211-Features:\r\n FEAT\r\n PASV\r\n EPSV\r\n SIZE\r\n211 End")
  63. case "USER":
  64. if cmdParts[1] == "anonymous" {
  65. mock.proto.Writer.PrintfLine("331 Please send your password")
  66. } else {
  67. mock.proto.Writer.PrintfLine("530 This FTP server is anonymous only")
  68. }
  69. case "PASS":
  70. mock.proto.Writer.PrintfLine("230-Hey,\r\nWelcome to my FTP\r\n230 Access granted")
  71. case "TYPE":
  72. mock.proto.Writer.PrintfLine("200 Type set ok")
  73. case "CWD":
  74. if cmdParts[1] == "missing-dir" {
  75. mock.proto.Writer.PrintfLine("550 %s: No such file or directory", cmdParts[1])
  76. } else {
  77. mock.proto.Writer.PrintfLine("250 Directory successfully changed.")
  78. }
  79. case "DELE":
  80. mock.proto.Writer.PrintfLine("250 File successfully removed.")
  81. case "MKD":
  82. mock.proto.Writer.PrintfLine("257 Directory successfully created.")
  83. case "RMD":
  84. if cmdParts[1] == "missing-dir" {
  85. mock.proto.Writer.PrintfLine("550 No such file or directory")
  86. } else {
  87. mock.proto.Writer.PrintfLine("250 Directory successfully removed.")
  88. }
  89. case "PWD":
  90. mock.proto.Writer.PrintfLine("257 \"/incoming\"")
  91. case "CDUP":
  92. mock.proto.Writer.PrintfLine("250 CDUP command successful")
  93. case "SIZE":
  94. if cmdParts[1] == "magic-file" {
  95. mock.proto.Writer.PrintfLine("213 42")
  96. } else {
  97. mock.proto.Writer.PrintfLine("550 Could not get file size.")
  98. }
  99. case "PASV":
  100. p, err := mock.listenDataConn()
  101. if err != nil {
  102. mock.proto.Writer.PrintfLine("451 %s.", err)
  103. break
  104. }
  105. p1 := int(p / 256)
  106. p2 := p % 256
  107. mock.proto.Writer.PrintfLine("227 Entering Passive Mode (127,0,0,1,%d,%d).", p1, p2)
  108. case "EPSV":
  109. p, err := mock.listenDataConn()
  110. if err != nil {
  111. mock.proto.Writer.PrintfLine("451 %s.", err)
  112. break
  113. }
  114. mock.proto.Writer.PrintfLine("229 Entering Extended Passive Mode (|||%d|)", p)
  115. case "STOR":
  116. if mock.dataConn == nil {
  117. mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
  118. break
  119. }
  120. mock.proto.Writer.PrintfLine("150 please send")
  121. mock.recvDataConn()
  122. case "LIST":
  123. if mock.dataConn == nil {
  124. mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
  125. break
  126. }
  127. mock.dataConn.Wait()
  128. mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
  129. mock.dataConn.conn.Write([]byte("-rw-r--r-- 1 ftp wheel 0 Jan 29 10:29 lo"))
  130. mock.proto.Writer.PrintfLine("226 Transfer complete")
  131. mock.closeDataConn()
  132. case "NLST":
  133. if mock.dataConn == nil {
  134. mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
  135. break
  136. }
  137. mock.dataConn.Wait()
  138. mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
  139. mock.dataConn.conn.Write([]byte("/incoming"))
  140. mock.proto.Writer.PrintfLine("226 Transfer complete")
  141. mock.closeDataConn()
  142. case "RETR":
  143. if mock.dataConn == nil {
  144. mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
  145. break
  146. }
  147. mock.dataConn.Wait()
  148. mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
  149. mock.dataConn.conn.Write([]byte(testData[mock.rest:]))
  150. mock.rest = 0
  151. mock.proto.Writer.PrintfLine("226 Transfer complete")
  152. mock.closeDataConn()
  153. case "RNFR":
  154. mock.proto.Writer.PrintfLine("350 File or directory exists, ready for destination name")
  155. case "RNTO":
  156. mock.proto.Writer.PrintfLine("250 Rename successful")
  157. case "REST":
  158. if len(cmdParts) != 2 {
  159. mock.proto.Writer.PrintfLine("500 wrong number of arguments")
  160. break
  161. }
  162. rest, err := strconv.Atoi(cmdParts[1])
  163. if err != nil {
  164. mock.proto.Writer.PrintfLine("500 REST: %s", err)
  165. break
  166. }
  167. mock.rest = rest
  168. mock.proto.Writer.PrintfLine("350 Restarting at %s. Send STORE or RETRIEVE to initiate transfer", cmdParts[1])
  169. case "NOOP":
  170. mock.proto.Writer.PrintfLine("200 NOOP ok.")
  171. case "REIN":
  172. mock.proto.Writer.PrintfLine("220 Logged out")
  173. case "QUIT":
  174. mock.proto.Writer.PrintfLine("221 Goodbye.")
  175. return
  176. default:
  177. mock.proto.Writer.PrintfLine("500 Unknown command %s.", cmdParts[0])
  178. }
  179. }
  180. }
  181. func (mock *ftpMock) closeDataConn() (err error) {
  182. if mock.dataConn != nil {
  183. err = mock.dataConn.Close()
  184. mock.dataConn = nil
  185. }
  186. return
  187. }
  188. type mockDataConn struct {
  189. listener *net.TCPListener
  190. conn net.Conn
  191. // WaitGroup is done when conn is accepted and stored
  192. sync.WaitGroup
  193. }
  194. func (d *mockDataConn) Close() (err error) {
  195. if d.listener != nil {
  196. err = d.listener.Close()
  197. }
  198. if d.conn != nil {
  199. err = d.conn.Close()
  200. }
  201. return
  202. }
  203. func (mock *ftpMock) listenDataConn() (int64, error) {
  204. mock.closeDataConn()
  205. l, err := net.Listen("tcp", mock.address+":0")
  206. if err != nil {
  207. return 0, err
  208. }
  209. tcpListener, ok := l.(*net.TCPListener)
  210. if !ok {
  211. return 0, errors.New("listener is not a net.TCPListener")
  212. }
  213. addr := tcpListener.Addr().String()
  214. _, port, err := net.SplitHostPort(addr)
  215. if err != nil {
  216. return 0, err
  217. }
  218. p, err := strconv.ParseInt(port, 10, 32)
  219. if err != nil {
  220. return 0, err
  221. }
  222. dataConn := &mockDataConn{listener: tcpListener}
  223. dataConn.Add(1)
  224. go func() {
  225. // Listen for an incoming connection.
  226. conn, err := dataConn.listener.Accept()
  227. if err != nil {
  228. // t.Errorf("can not accept: %s", err)
  229. return
  230. }
  231. dataConn.conn = conn
  232. dataConn.Done()
  233. }()
  234. mock.dataConn = dataConn
  235. return p, nil
  236. }
  237. func (mock *ftpMock) recvDataConn() {
  238. mock.dataConn.Wait()
  239. io.Copy(ioutil.Discard, mock.dataConn.conn)
  240. mock.proto.Writer.PrintfLine("226 Transfer Complete")
  241. mock.closeDataConn()
  242. }
  243. func (mock *ftpMock) Addr() string {
  244. return mock.listener.Addr().String()
  245. }
  246. // Closes the listening socket
  247. func (mock *ftpMock) Close() {
  248. mock.listener.Close()
  249. }
  250. // Helper to return a client connected to a mock server
  251. func openConn(t *testing.T, addr string, options ...DialOption) (*ftpMock, *ServerConn) {
  252. mock, err := newFtpMock(t, addr)
  253. if err != nil {
  254. t.Fatal(err)
  255. }
  256. defer mock.Close()
  257. c, err := Dial(mock.Addr(), options...)
  258. if err != nil {
  259. t.Fatal(err)
  260. }
  261. err = c.Login("anonymous", "anonymous")
  262. if err != nil {
  263. t.Fatal(err)
  264. }
  265. return mock, c
  266. }
  267. // Helper to close a client connected to a mock server
  268. func closeConn(t *testing.T, mock *ftpMock, c *ServerConn, commands []string) {
  269. expected := []string{"FEAT", "USER", "PASS", "TYPE"}
  270. expected = append(expected, commands...)
  271. expected = append(expected, "QUIT")
  272. if err := c.Quit(); err != nil {
  273. t.Fatal(err)
  274. }
  275. // Wait for the connection to close
  276. mock.Wait()
  277. if !reflect.DeepEqual(mock.commands, expected) {
  278. t.Fatal("unexpected sequence of commands:", mock.commands, "expected:", expected)
  279. }
  280. }
  281. func TestConn4(t *testing.T) {
  282. mock, c := openConn(t, "127.0.0.1")
  283. closeConn(t, mock, c, nil)
  284. }
  285. func TestConn6(t *testing.T) {
  286. mock, c := openConn(t, "[::1]")
  287. closeConn(t, mock, c, nil)
  288. }